Pull to refresh

Comments 65

Я замечал, что есть героические люди которые могут просто сходу поправить свой код написанный и заброшенный… пару лет назад. Я же как-то дошел до того, что утром не мог понять, что я накодил вчера. ))) И да — комментарии для самого себя стали спасением.
Проблема что некоторые не любят комменты видимо банальна — если это относится к рабочим процессам — то проект сдан, или программист уволился — и это не его проблемы. А если к собственным проектам — то и так всё в голове пока есть интерес.
Думаю в open source сообществах дело с комментами должно обстоять лучше — но сам опыта такого не имею, было бы интересно если бы кто-то написал как там на самаом деле.
так посмотрите на гихабе любой интересный Вам проект и сразу все станет понятно )

Вот интересный мне проект, например: Elixir (ссылка просто на первый файл, там везде так).


Скажете «ну это же целый язык!»? Ну вот ссылка уже на мою библиотеку, тоже случайный файл.


Что должно быть понятно?

Вы написали отличный пример мусорных комметариев, удалив которые можно значительно улучшить качество кода за счёт уменьшения его размера в два раза, убрав необходимость в синхронизации двух одинаковых блоков кода написанных зачем-то на разных языках.

Кстати, улучшить вашу идею и убрать её недостатки можно заменив бесполезные комментарии на названия переменных, которые потом будут использоваться:

class PhoneService
{

//Функция очистки телефона, принимает строку
    //Перечислим символы которые нам нужно удалить из строки
    //Очистим строку от символов
    //Стандартизируем номер телефона, если в начале 8, то заменим ее на 7
    //Вернем очищенный телефон

}

===>

public function clear_phone(string $phone) {
	$symbols_for_delate = ??;
	$cleared_phone = ??;
	$standarded_phone = ??;
	return $standarded_phone;
}


Первый комментарий не нужен — он уже и так написан английским языком. Теперь нам надо вывести следующую переменную на базе предыдущей.
Думаю, это неплохой подход для всяких джунов, у которых ещё не собралось в голове понимание в кучу.
Я бы даже начал с названия функции. «get_phone_standard_representation» куда понятнее.
normalize_phone тоже как вариант
улучшить качество кода за счёт уменьшения его размера в два раза
Предлагаете измерять качество кода его размером? Ну так, спорная практика.
необходимость в синхронизации двух одинаковых блоков кода написанных зачем-то на разных языках.
Два разных языка — это Вы про собственно код и комментарии к нему? Так комментарии в любом случае должны соответствовать коду, на то их и пишут.
Предлагаете измерять качество кода его размером? Ну так, спорная практика.
Одна из метрик.

комментарии в любом случае должны соответствовать коду, на то их и пишут.
Ну это плохие комментарии.
$symbols_for_delate
Простите, функция действительно должна только очищать номер телефона?
Delate — доносить
Я лишь хотел бы отметить неудачное название функции «clear_phone». Лучше было бы что-то типа «cleanup_phone_number» или «standardize_phone_number». Ну и параметр переименовать тоже.

Тут много к чему можно придраться.
Вместо «standardize», больше подходит «normalise»
«symbols_for_delete» — неграмотно, надо «symbols_for_deletion» или «symbols_to_delete»

Кроме того, зачем-то есть список символов, которые нужно удалять. Что если придёт символ, которого нету в списке?
Почему просто не удалить «все что не цифра» регекспом?
Текущее описание в «идея» нa это никак не намекает.
Если это был «простой пример» — то надо либо поменять код, либо «идею»
Иначе плохой пример.

Нужно очистить номер телефона от символов вроде '+,-, скобок и пробелов', так, чтобы на выходе получился только набор цифр. Фича нужна для реализации поиска по номеру телефона — введенного любым образом

Текущее описание прямо намекает на то, что нужно удалять весь мусор, регуляркой \D.

Вы слышали что-нибудь про TDD?
По сути вы можете сначала написать тесты, в которых будет все возможное поведение вашей функции, затем уже саму имплементацию.

Лично у меня так не получается писать, я несколько раз пробовал так делать, но у меня все равно не получается сначала учесть все ньюансы, а потом написать код. Обычно я пишу функцию, а потом пишу на нее несколько тестов, смотрю на них и на ум приходит еще несколько вариантов, из-за которых я вношу правки в функцию. То есть лично мне проще сначала подумать над алгоритмом как это должно работать в принципе, а потом подумать над ньюансами. Хотя справедливости ради мой код так часто переписывается, что с актуальностью многих тестов постоянно беда.

смотрю на них и на ум приходит еще несколько вариантов, из-за которых я вношу правки в функцию.
С TDD как раз подходящее время чтобы написать тест на новые случаи, проверить что он не проходит и написать реализацию.

Для того, чтобы увидеть новые случаи я провожу в уме дебаг функции обдумывая каждую строчку на то, какие ньюансы тут могут произойти.

Тесты тоже нужно документировать. Ну увижу я тест, из которого видно, что перед записью в базу номера из него удаляются пробелы. А зачем непонятно. Может место экономят, например.

Хорошо написанные тесты (вроде как) и есть документация. По ним нужно смотреть, какое поведение ожидается. А на вопросы «зачем» — отвечают внятные имена переменных и функций, и в целом контекст.
TDD показывает, что мы должны получить на выходе, в зависимости от того что пришло на вход, но никак не определяет как именно мы это получим. Описанный в статье подход, так называемый «псевдокод», как раз помогает проработать детали реализации. Т.е. TDD и псевдокод решают разные задачи.

Как уже некоторые отметили, такие комментарии действительно дублируют код. От таких комментариев (да еще и на другом языке) нужно избавляться.


Комментарии нужны, когда самодокументируемый код не справляется с заложенной там семантикой, и/или есть неочевидные способы решения, комментарии в таком случае должны подсказывать почему был выбран именно такой подход.


А от такого комментирования пользы никакой:


// модель пользователя
class UserModel {
   // инициализуем
   constructor(id, name) {}
   // взять данные
   getData() {}
   // взять кешированные данные
   getCachedData() {}
}
Я подобным образом приучаю своих разработчиков писать код, алгоритм также пошагово разбивается — 1 шаг это одна строка в комментариях, а потом каждый шаг превращается в 1 метод с нормальным говорящим названием. Таким образом получаем читаемый код, который не требует комментариев (функция получается 10-15 строчек). Таким образом пытаюсь выбить привычку, сначала в один метод накидать всю логику, добиться ее работоспособности, а потом начинать думать, а как же бы ее так разбить, чтобы код остался читаемым без комментариев.
Я подобным образом приучаю своих разработчиков писать код, алгоритм также пошагово разбивается — 1 шаг это одна строка в комментариях, а потом каждый шаг превращается в 1 метод с нормальным говорящим названием.

Почему сразу не писать названия методов, вместо комментариев?

Потому что алгоритмы формализуются, оптимизируются и обсуждаются на естественном человеческом языке, а уже потом переводятся в код.

Вся проблема в наименовании классов/методов/свойств в том чтобы назвать его так, чтобы без комментариев было понятно что происходит и при этом постараться сохранить терминологию во всем проекте, а не раздувать ее (например в одном месте используем user, в другом client, а подразумевается одно и тоже)

На codereview могут указать на неправильную терминологию. А переименовать ее достаточно легко — ide помогут.

Мне кажется, данные комментарии есть в названиях самих переменных, смысла их писать нет, если только вы не преподаватель по программированию для первых курсов. Есть смысл ставить комментарии только для того чтобы сказать пользователю о том что именно делает ваш метод или чтобы отметить особо сложный код, например сложную реализацию хеширования, желательно со ссылкой на страницу статью о данном алгоритме.
Я бы написал так:


//Приводит номер телефона к нормализованному виду. Пример: 79998889988
//$phone - номер телефона в виде: +7 (999) 888 99 88, 8-999-888-99-88 или 89998889988
public function normalize_phone(string $phone){
    $symbols_for_delate = ['+','-','(',')', ' '];
    $phone = str_replace($symbols_for_delate, '', $phone);
    if( $phone[0] == '8' ){
      $phone[0] = '7';
    }

    return $phone;
  }
Кстати, в идеале эти комментарии тоже можно убрать, через типизацию аргумента и возврата. И практика и теория указывает, что багов будет намного меньше:
public function normalize_phone(UnnormalizedPhone $phone){
    $symbols_for_delate = ['+','-','(',')', ' '];
    $normalized = str_replace($symbols_for_delate, '', $phone->value);
    if( $normalized[0] == '8' ){
      $normalized[0] = '7';
    }

    return new NormalizedPhone($phone);
  }

Этот комментарий просто переедет к типам NormalizedPhone и UnnormalizedPhone, да и мы понимаем, что этот код только лишь для примера, если вы серьезно хотите поработать с номерами телефонов, то там все чуть сложнее.

Если уж использовать классы, то логика нормализации должна быть внутри класса NormalizedPhone, а не в функции normalize_phone. Ибо инкапсуляция.
class NormalizedPhone {
    private $phone;
    public function __construct($phone) {
        $this->phone = $phone;
    }
    public function __toString() {
        $normalized = str_replace(['+','-','(',')', ' '], '', $this->phone);
        if( $normalized[0] == '8' )
            $normalized[0] = '7';
        return $normalized;
    }
}
echo new NormalizedPhone("+7(999)123-45-67");

Почему у вас нормализация в __toString, а не в конструкторе? Так у вас при каждом преобразовании в строку будет выполняться нормализация. А если в конструкторе, то один раз — при создании.
Зачем вам в этом конкретном случае хранить сырое представление телефона?

Код в конструкторе это нехорошо. Конструктор он чтобы корректно создать объект, а не выполнять код.

Собстно можно и в конструктор перенести, моя основная мысль была не про это, а про то что если уж TheShock предлагает классы вместо функции, то надо их и использовать как классы, в смысле переделать функцию на классы, а не оставлять все вместе, и функцию и класс

Насколько я понял, TheShock предлагает классы не для замены функции, а для того, чтобы с помощью типов обозначить что принимает и что возвращает эта функция.
Это совсем не значит, что тут сразу же нужно наворачивать конструкторы и переопределять __toString.

если уж TheShock предлагает классы вместо функции
Не обязательно. Я говорю о статической типизации. Она есть что в ФП, что в ООП
Зачем вам в этом конкретном случае хранить сырое представление телефона?
Данные всегда лучше хранить сырыми. Если у вас есть проблемы с производительностью именно в этом методе — всегда можно кешировать результат. Единственно, конечно, в том примере toString — неудачное использование метода. На TS можно было бы сделать как-то так, а если, вдруг, будут проблемы с производительностью — раскомментировать мемоайз:

const NotNumberRegexp = /[^\d]/g;

class NormalizedPhone {
  constructor (
    public readonly value: number
  ){}
}

class UnnormalizedPhone {
  constructor (
    public readonly value: string
  ){}

  // @memoize
  get normalized () {
    let normalized = this.value.replace(NotNumberRegexp, '');

    if (normalized[0] == '8') {
      normalized = '7' + normalized.substr(1);
    }

    return new NormalizedPhone(Number(normalized));
  }
}

console.log(new UnnormalizedPhone("+7(999)123-45-67").normalized)


Так же можно сделать в функциональном стиле, но без брендинга будут проблемы с типизацией (хотя это особенности реализации TS):
const NotNumberRegexp = /[^\d]/g;

type UnnormalizedPhone = string;
type NormalizedPhone = number;

function normalizePhone (phone: UnnormalizedPhone) {
  const cleared = phone.replace(NotNumberRegexp, '');

  const normalized = cleared[0] == '8'
    ? ('7' + cleared.substr(1))
    : cleared;

  return Number(normalized) as NormalizedPhone;
}

var normalizedPhone = normalizePhone("+7(999)123-45-67" as UnnormalizedPhone)

console.log(normalizedPhone);


Конечно, возле `type UnnormalizedPhone` теперь нужно в комментарии описать, что он содержит. Но не в каждой функции, которая ожидает `UnnormalizedPhone` или `NormalizedPhone`
Можно ещё упростить, если убрать ненужную переменную с длинным названием

//Приводит номер телефона к нормализованному виду. Пример: 79998889988
//$phone - номер телефона в виде: +7 (999) 888 99 88, 8-999-888-99-88 или 89998889988
public function normalize_phone(string $phone){
    $phone = str_replace(['+','-','(',')', ' '], '', $phone);
    if( $phone[0] == '8' )
      $phone[0] = '7';
    return $phone;
  }

Не путайте уменьшение количества кода и упрощение.
Убирание объясняющих переменных уменьшает количество кода, но не упрощает функции.

Вот такая переменная $symbols_for_delate нафик ненужна, слишком длинная, мало что объясняет и используется ровно в одном месте. Ещё и с ошибкой написана.
Мне кажется, что некоторые читатели слишком буквально восприняли идею использовать псевдокод вообще всегда, включая написание функции в три строчки.

Псевдокод хорош когда логика заковыристая, и метод строк на 50...100 и больше. Резонное замечание — в таких случаях применяют декомпозицию. Да, естественно. Но с псевдокодом сделать декомпозицию, поправить интерфейсы, дата-контракты и архитектуру классов можно ещё до того, как начали собственно писать код, а не тогда когда написали и решили что «получается как-то сложновато, надо порефакторить».

При использовании псевдокода никто не заставляет писать код построчно под каждым комментарием, равно как и оставлять комментарии к коду, который самоочевиден. Это вообще не серебряная пуля, а инструмент, который надо применять с умом. Как молоток — хорошая штука, но надо уметь использовать, если не хочешь бить по пальцам.

Автор не привёл примера посложнее, хотя не помешало бы, и тему не очень раскрыл. Но это не значит, что псевдокод плохая штука.

А насчёт «хороший код в комментариях не нуждается» — ну, это хороший, а на практике работать приходится с разным, да и свой получается не всегда идеальным, чего уж там.

Всем добра!
А насчёт «хороший код в комментариях не нуждается» — ну, это хороший, а на практике работать приходится с разным, да и свой получается не всегда идеальным, чего уж там.
А на практике люди, которые пишут плохой код — комментарии пишут ещё хуже

У автора проблема в том, что он оставил в конечном варианте плохие комментарии. Если бы автор сначала написал комментарии, а потом написал по ним код и удалил комментарии, то возможно никто бы ничего не сказал. А самое главное, что нужного комментария как раз нет, который бы описывал то, что именно делает функция. Из комментария не понятно, что за очистка телефона такая. То есть в конечном варианте автор оставил не нужные комментарии и не оставил нужных.

UFO just landed and posted this here
Спасибо, не встречался с ним. Обязательно изучу — описание выглядит интересно

Абстрагируясь от технических деталей приведенного в статье примера, не совсем понятно почему достаточно известный "Процесс программирования с псевдокодом (ППП)" (Глава 9, "Совершенный код" С. МакКоннелла) заменен на "Парадигму разработки через комментирование. Idea. Comment-Design. Code. (IC-DC)"?


Если вы задаетесь этим вопросом — то вам просто необходимо, в срочном порядке, прочитать "Совершенный Код" Стив Макконнелл. Там данная концепция более чем раскрыта.

Вдвойне странно, что автор статьи упоминает эту книгу. То есть по сути вместо этого можно было сослаться на конкретную главу в книге (тем более там описан не только сам подход, но и указаны его альтернативы). А также детальнее проработать сам пример примения с технической точки зрения (как минимум добавив методам и переменным более выразительные имена) или найти более подходящий пример где комментарии не являются избыточными и несут дополнительный смысл.

Принцип что комментарий должен описывать почему этот код такой, а не что он делает забыт?

Зачем описывать простейший алгоритм комментарием? Код это сделает надежнее и понятнее. А вот написать:
//Обрабатываем только форматы +7(999)123-45-67 и 8(999)123-45-67 на все остальное кидаем исключение, т.к. пользователи вводят чушь которую мы успешно интерпретируем как телефон и потом возникают проблемы. (ссылока не тикет с таким случаем)

имеет смысл
Да, я согласен. Данный комментарий очень информативен, но тогда мы не будем использовать проектирование через комментарии.

P.S. ох если бы подобные комментарии существовали хотя бы на одном проекте унаследованном мной. Наверное я был бы очень счастлив и подобной статьи бы не случилось :)
У нас так лабы в универе сдавали те, кто очень плохо в программирование умел.

В остальном выглядит как вредные советы. Если в теле функции нужны комментарии, то это скорей всего признак того, что код нужно переписать. Во всех остальных редких случаях это действительно сложная и/или специфичная логика, требующая пояснений.

Ну а про вариант с псевдокодом уже выше упоминали.
(Все — ИМХО.) Комментарии в коде — это следствие одной из трех вещей (а часто комбинация):
1. Боязнь получить вопросы на код-ревью (часто — от ревьюверов, которые недостаточно заинтересованы, чтобы чуть подразобраться, прежде чем спрашивать). Т.е. как бы превентивный ответ на эти [бесполезные?] вопросы.
2. Бедное именование переменных/методов. Если с именованием все в порядке, то комментарии чаще всего не нужны.
3. Отсутствие нормального покрытия тестами, т.е. «боязнь регрессии» («кто-нибудь придет потом и натопчет тут, все сломает»).

За скобками также бедная архитектура (простой код не нуждается в комментариях, а сложного кода почти никогда не бывает при хорошей архитектуре), но это из разряда «лучше быть богатым и здоровым чем бедным и больным» или «ломиться в открытую дверь».
  1. Практика показывает, что комментарии или имена, говорящие зачем мы что-то делаем, нужны или в коде, или в тестах, а лучше и там, и там. Тот кто удаляет "ненужный" код, удалит или поправит и "ненужный" тест
от ревьюверов, которые недостаточно заинтересованы, чтобы чуть подразобраться

Если ревьюеру нужно «подразбираться» — дело швах, есть там комментарии, или их нет.

Как первоначальное описание идея хорошая, но комментарии имеют свойство устаревать, или хуже, когда комментарий поменяли, а логику нет. Конечно это может отлавливатся на ревью, но все ошибаются.


Так же такой подход может спровоцировать длинные методы. Из-за комментариев сложно будет уследить за длинной самого метода и пропорций код/комментарии.


Это действительно полезно на этапе проектирования, но потом должно уходить в документацию (конфлюенс/javadoc)

В защиту автора. Ещё ни разу не видел коммерческий код, который документировал бы сам себя. Вероятно, что мне не везло. А вообще, смысл статьи не в том, чтобы писать комментарии, а в том, чтобы их написать ДО кодирования. Сформировать идею. Если вам так мешают комментарии, по окончанию можете их удалить.
Да, вы абсолютно верно поняли.

Увидев тут, здравые замечания — я скорее всего реализую возможность ведения коммент базы- для тех кому они мешают непосредственно в коде.

Над реализацией еще подумаю — но сама идея будет заключаться в удалении комментариев из кода, с последующим хранением их в базе(например в файловой yaml_db), по команде из терминала. Ну и соответственно возвратом их в код, по команде.

Как вы эту коммент-базу будете синхронизировать с системой контроля версий?

А что, система контроля версий уже не канает как база для хранения комментариев к измеренному коду?

Когда то может пару раз тоже так делал и сейчас в урезанном виде наверно использую, на каких то сложных задачах (для меня), это упрощало работу, теперь буду знать как это называется).

Например делаю класс и комментариями пишу методы какие нужны (что должны получить или сделать), к некоторым методам приписываю какие внутри должны вызываться, это помогает например если за день всё не можешь сделать на следующий день будет ясно что нужно реализовать и общий план по коду.
Мне кажется у автора просто пример слишком простой, а вообще такой метод для меня полезен.

Что бы не переписывать с нуля, портянку старого чужого кода, бывает просматриваю по кускам и сам добавляю комментарии, если задача делается не один день то это полезно, пришел на другой день ещё пересмотрел код закомментировал, а потом в итоге можно сказать чужой код становится твоим.

Список кодов по странам
А теперь угадайте, что сделает этот код?


//Перечислим символы которые нам нужно удалить из строки
$symbols_for_delete = ['+','-','(',')', ' '];
//Очистим строку от символов
$phone = str_replace($symbols_for_delete, '', $phone);

   //Стандартизируем номер телефона, если в начале 8, то заменим ее на 7
if( $phone[0] == '8' ){
$phone[0] = '7';
}


Правильный ответ

Поломает все номера из азиатского региона.
Потому что нельзя сперва отбросить часть условного префикса переменной длины, а потом сделать абсолютную замену первого символа.

Так просто заменять 8 на 7 неправильно. Надо понимать, что за этим стоит.
8-10-7-903-XXX-XX-XX
8 — выход на междугороднюю связь
10 — выход на международную связь
7 — код страны
903 — код города или оператора
XXX-XX-XX — телефонный номер абонента


Для упращения код 810 заменили на +.
Таким образом, номер с +7 это полный международный номер, а номер начинающийся на 8 это номер в пределах страны. Для Греции например код страны 30 и звонить можно как по номеру 81030… так и по +30… И соответственно, если пользователь ПО в Греции, то 8 должно заменятся на +30, а не на +7.

Если код содержит много комментариев, то это может означать, что качеству самого кода не уделено достаточно внимания. Например, неудачно разбит на методы(в поле зрения разработчика на попадает весь квант логики, приходиться "комментировать"), заложена большая цикломатическая сложность(поясняем, что происходит в третьем вложенном if), переиспользуются переменные(поясняем, что это теперь уже не строка из БД, как двумя экранами назад, а реконструированный из нее data object), ...


Но это не самая большая беда комментариев. На взрослых проектах кодовая база велика, разработчиков много, задач в трекере много, нельзя гарантировать, что один и тот же разработчик может закрыть навсегда какой-то функционал. Он меняется совместно с нуждами бизнеса, где-то уже переиспользуются. Что-то делается в режиме — "Вася в отпуске, нужно вчера, глянь". В общем, комментарии менять просто забывают. Т.е. в код добавляется потенциально ещё одно место, где человек может ошибиться. А значит — ошибка лишь вопрос времени. В итоге, на существенной части кодовой базы содержание комментариев не соответствует коду. Либо просто бессмысленно в силу того, что нашлось более удачное название представления бизнес-сущности в коде.


Реально полезный кейс — это пояснение выбора той или иной логики решения, введение в контекст. Например, в силу переезда с одного технического решения на другое — появляется временная двойственность Api. Во всех новых реализациях используется новое Api, но вот тут понадобилось поддержать обратную совместимость там, где новое Api ещё не достаточно протестировано/не реализует функционал/… В Git будет видно, что коммит новый, а Api используется старое. В итоге, без осознания — "почему", рефакторинг с заменой на новое — убьет кусок кода и бог его знает, где и когда это всплывёт.


В основном, это нужно редко, ибо профессиональный разработчик либо знает бизнес-домен, либо все равно в процессе разбора и будет спрашивать старших товарищей.


Такие дела.

Sign up to leave a comment.

Articles