Pull to refresh

Comments 129

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

«Номер два» меньше думает о сложности системы в целом и больше о локальных задачах, поэтому в краткосрочной перспективе его подход эффективнее, но в долгосрочной логика обработки пользовательских действий в большей степени «размазывается» и неизбежно начинает повторяться, явно или неявно, что ведёт к тому, что исправлять логические ошибки или менять поведение системы становится труднее, так как приходится пересматривать и менять почти одинаковый код в разных местах.
А мне кажется (если я правильно понял описание в топике), что подход номера один — обычное быдлокодерство, которое противоречит основам рефакторинга и хорошей архитектуры и это совершенно не зависит от типизированности или многопоточности. Просто на разные кнопки вешать одну и ту же функцию, в которой ифами разделять кнопки — это странно, если можно навесить разные методы, каждый из которых будет уже сам решать, что и как делать.
Поддерживаю, тоже голосую за «второго» — если и я правильно понял автора. «Упаковывать максимум логики» никак нельзя в «стратегии» (плюс не помню, что бы в DRY было такое определение — «упаковывание логики»).
Можно и один обработчик — но лишь со стратегией внутри.
А про «крупные проекты», тоже повеселило — такой код с («упакованной» логикой :)) будет подходить лишь для «integration testing», но никак не для «unit testing», и плюс нарушает «Single responsibility principle». Вообщем — второй молодец…
Согласен, есть даже такой антипаттерн — Long method см. Code_smell.
А как же EventDriven? При создании UI это очень распространенный подход.
Там как раз одна точка для обработки однотипных сообщений. Например метод для клика кнопки или выбора пункта меню. А дальше обработка по типу кнопки например.
Посмотрел.
В этих случаях второй или отправляет разные события (обычно).
Или подписавшись на одно событие делает три разных метода в зависимости от входных параметров.

Первый почти всегда делает один метод.
Было бы неплохо увидеть хоть какие-то абстрактные примеры того, о чём говорится в статье. Иначе очень сложно понять, о чём вообще речь.
Как я понял, дело в чем-то вроде такого:

«первый» (30 строк):
function saveOrSaveAsOrMergeDialog(..) { 
}


«второй» (100 строк):
function saveDialog(..) { 
}

function saveAsDialog(..) { 
}

function mergeDialog(..) { 
}
А откуда у вас взялось 30 строк и 100 строк? Скорее так:

«первый» (90 строк):
function saveOrSaveAsOrMergeDialog(..) { 
  // много лапшакода, куча ветвлений:
  // 30 строк кода отвечающих за сохранение 
  // 30 строк кода отвечающих за мердж 
  // 30 строк кода отвечающих за всё остальное
}


«второй» (110 строк):
function saveDialog(..) { 
  saveUtils();
}

function saveAsDialog(..) { 
  saveUtils();
  otherUtils();
}

function mergeDialog(..) { 
  mergeUtils();
  saveUtils();
}

function saveUtils (){
  // 30 строк кода отвечающих за сохранение 
}

function mergeUtils (){
  // 30 строк кода отвечающих за мердж 
}

function otherUtils (){
  // 30 строк кода отвечающих за всё остальное
}
Зачем тогда разводить столько философии? Вы часом не первый программист? :)
Точно нет, я вообще аналитик :)
А почему тогда первый подход имеет хоть какой-то право на существование? Какой смысл в функции saveOrSaveAsOrMergeDialog? Какой смысл в функции, которая умеет делать разные действия? Приведите, пожалуйста, хоть какой-нибудь более конкретный пример, где первый подход можеть быть хорошим
Ну да, микроконтроллеры и т. д. А с обычной точки зрения первый подход может иметь смысл?
Кстати, на мой взгляд, второй код будет быстрее (хотя будет занимать меньше места в памяти). Потому что не тратится время на if'ы. Поэтому даже с точки зрения оптимизации, нужен второй подход, а не первый, т. к. на обычных компах важнее быстродействие, а не расходы памяти. Да, я знаю, что время, которое тратится на if, ничтожно, но ведь и память, которая выигрывается при первом подходе, тоже ничтожна
На самом деле, есть еще такая беда, как необходимость формирования стека при вызове функции (на низком уровне не так все просто работает), поэтому является ли замена if-а на вызов функции выигрышем или проигрышем — это еще большой вопрос.
На современных микроконтроллерах flash-памяти, кстати, обычно много. С оперативкой и быстродействием проблем больше.
Отсутствие копипасты для сохранение. Некоторые вот так понимают DRY.
Во втором коде нет копипасты. Вся общая функциональность вынесена в отдельные функции
Я про первый. там похоже (по описанию) тоже нет, часть кода для сохранения используется во всех трех сценариях. И может даже более эффективно (в плане проца и памяти) чем во втором.
Могу предположить, что так исторически сложилось.
Сначала у обоих был только Save.
Потом понадобилось добавить SaveAs. Первый это сделал как параметр функции, второй выделил отдельную функцию.
Потом понадобилось добавить Merge. Первый сделал как и в первый раз, второй также сделал как и в первый раз.

пс. Не агитирую за какой-либо подход, оба имеют право на существование при определённых масштабах и задачах.
Вопрос больше в том, когда рационально переключаться с первого метода на второй (или обратно).
30 взялось из текста статьи, 90 из головы, как и весь пример. Но я предположу, что количество строк кода «второго» всегда выше на одну и ту же задачу.
ну и конечно же, у «первого» не будет
// 30 строк кода отвечающих за сохранение
// 30 строк кода отвечающих за мердж

потому как он использует один и тот же метод для создания диалога, только с разными параметрами, первые 30 и вторые 30 строк сольются в один блок, который еще потом будет заоптимизирован и с помощью дополнительных ветвлений или рекурсии сделан универсальным.
Да, так, у Дениса почти всегда больше кода на одну UI задачу.
срочно замените имя на «второго»! :)
Да, конечно же Денис === «номер два».
Ну тут смотря как считать. При первом подхоже объёи кода растёт линейно в зависимости от функционала, а во втором, на повторяющихся тасках, объём кода будет вести себя как sqrt(n) — сначала надо написать обёртки и общие функции, код которых в результате больше, чем если бы вставлять код на место вызовов, но в похожих тасках вызываются уже существующие методы, что значительно уменьшает количество кода.
Хорошо объем кода оценивать не количеством строк, а временем на его понимание. Как я понял, код «первого» побил кучу рекордов — 1 рабочий день мидла на 30 строк кода.
Я это написал конкретно в защиту 2го программиста на тему объёмов кода. А по поводу измерений проделанной работы объёмом кода — достаточно вспомнить индокод, чтобы всё встало на свои места.
Вот именно.
По моему опыту, непосредственно написание кода составляет очень малую часть работы программиста и увеличение объема кода на 20% почти не скажется на общем времени выполнения, а основное время уходит на продумывание общей структуры кода, алгоритма работы и дебаг. Написать короткую функцию без логики — 20 секунд. Чем сложнее логика, тем больше удельного времени приходится на каждую строчку кода — в десятки-сотни-тысячи раз больше.
ну и конечно же, у «первого» не будет

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

потому как он использует один и тот же метод для создания диалога, только с разными параметрами

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

который еще потом будет заоптимизирован и с помощью дополнительных ветвлений или рекурсии сделан универсальным.

Это вы вообще странное что-то написали. Как можно с помощью рекурсии сделать метод универсальным? О чём вы вообще? Рекурсия — это только часть алгоритма.
ужс, какое буквоедство. Мы говорим об абстрактной сферически-вакуумной ситуации, а не реальном мерже. Но уж поверьте, перфекционисты заоптимизируют любой код до неузнаваемости, в ущерб читабельности и будет там не 3 блока, по 10 строк, а один клубок из 30.

Это вы вообще странное что-то написали. Как можно с помощью рекурсии сделать метод универсальным? О чём вы вообще? Рекурсия — это только часть алгоритма.

Это жесткий маневр, когд обработав часть метода мы вызываем его же с другими параметрами. Например, один метод отвечает за saveAs и save, но автор решает в ходе проверок, что ему легче в случае с saveAs вызвать этот метод еще раз, чтобы он сделал save — это иногда наблюдается у любителей языков с БП, чтобы избежать множества вложений if/else.

P.S. Только не надо мне писать, что это притянуто с потолка, пример снова таки абстрактный :)

P.P.S. БП = безусловный переход, в просторечьи goto
Это жесткий маневр, когд обработав часть метода мы вызываем его же с другими параметрами. Например, один метод отвечает за saveAs и save, но автор решает в ходе проверок, что ему легче в случае с saveAs вызвать этот метод еще раз, чтобы он сделал save — это иногда наблюдается у любителей языков с БП, чтобы избежать множества вложений if/else.

Что за дикость? Вы хоть представляете такую ересь? Если у первого такой код, то не удивительно, что второй не может в нём разобраться.
Конечно представляю, мне как-то приходилось такое саппортить :)
Всецело поддерживаю, что так писать нельзя, а «второму» положено еще и молоко за вредность производства
А ведь может быть компромиис
function saveOrSaveAsOrMergeDialog(..) { 
  if (...) {
    saveUtils();
  }
  else if (...) {
    saveUtils();
    otherUtils();
  }
  else if (...) { //  if на всякий случай
    mergeUtils();
    saveUtils();
  }        
}

function saveUtils (){
  // 30 строк кода отвечающих за сохранение 
}

function mergeUtils (){
  // 30 строк кода отвечающих за мердж 
}

function otherUtils (){
  // 30 строк кода отвечающих за всё остальное
}
Помирить Дениса и Никиту :) для одного почти ничего не изменится, кроме выноса логики в дополнительные функции и оставления в основной функции только выбора логики. А второй сразу видит, что это обработчик не одного события, а нескольких и изменять saveUtils будет аккуратно, заглянув и в код mergeUtils и otherUtils — мало ли они используют какие-то побочные эффекты saveUtilsю

Проблема, как я понимаю именно в том, что в первый код вносятся изменения исходя из предположения, что он обрабатывает только одно событие одного контрола (второму программисту даже в голову не может прийти, что может быть по другому), а тут будет достаточно, по-моему, ясный намек, что не всё так просто.
ИХМО лучшее.
Сразу видно какие сценарии могут быть обработаны функцией «saveOrSaveAsOrMergeDialog» и как они точно работают.
Приверженцы ООП плачут горькими слезами при виде такого кода. :)

Если серьёзно, то не особо понятно зачем нужен огромный метод. Какую часть доменной логики он собой выражает? Разве в ней есть действие saveOrSaveAsOrMerge? Тут можно обойтись более простыми и близкими к домену маленькими методами.
Это скорее логика UI, а там может быть что угодно :)
Может кто нибудь пояснить, что хотел сказать автор? Или это просто поток сознания который не имеет никакой цели?
Используйте, пожалуйста следующие дефайны
#define ВАСЯ «программист номер один»
#define ПЕТЯ «программист номер два»
Ваш код… тьфу! Ваш текст будет читаться значительно проще!
А за «первым» не замечали странной тяги к постоянным оптимизациям уже работающего кода, выделению повторяющихся кусков в универсальные библиотеки? Имхо, на лицо перфекционизм в чистом виде. Конечно, надо заметно больше усилий для «более быстрого, универсального и красивого кода», но за счет большего опыта и умения быстро писать, производительность «первого» выходит даже больше, чем «второго». Это хороший пример программиста-одиночки, которому плохо в команде, потому что его части кода похожи на произведение искусства, но их тяжело саппортить кому-либо, кроме него самого.
А самое неприятное, что производительность таких программистов в команде не складывается линейно. Если их набрать 3-4 человека, то будут работать лишь в 2 раза быстрее, чем работал бы один.
Тоже очень интересно, хорошо, если автор все-таки прокомментирует.
Да, в целом у «первого» гораздо больше реализованных задач, и больше же «сразу» закрытых багов. У «второго» багов закрыто примерно столько же, но примерно две трети — повторные баги. В то время как у первого баги закрываются сразу же.
А можно пример по поводу повторных багов? Поработаю «адвокатом» )
Вообще, иногда они возникают при правке используемой во многих местах функций. (которых при 2м подходе много, в 1м же их нет). В результате тестеры присылают 2 десятка совершенно одинаковых багов, в том числе и прилепленных к старым не связанным, как повторные.
Не, я бы сказал, что второй фейлит одну и ту же задачу, но с разных «точек входа». В то время как первый если вообще фейлит один из Use Case'ов метода (что бывает с ним крайне редко), то исправляет сразу же, не допуская ошибок в других.
Ой, имелась в виду вот эта часть:
А за «первым» не замечали странной тяги к постоянным оптимизациям уже работающего кода, выделению повторяющихся кусков в универсальные библиотеки?
Второй тоже часто выносит в Helper'ы повторяющиеся куски. Существенных различий не вижу.
Нет. Речь идет именно о отдельных библиотеках, а не об отдельных кусках кода, вынесенных в методы.
производительность людей в команде вообще не складывается
скажем так, сумма производительностей разработчиков — это теоретический (и не достижимый) максимум производительности суммы разработчиков. Насколько близко к максимуму подбирается команда зависит от качества управления командой.
расскажите, какие у вас были практические результаты подбирания к этому максимуму. Вот канат 8 человек перетягивают с эффективностью лишь 51% от суммы отдельных людей.

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

Кстати есть ситуация, когда «производительность» двух человек такие может быть больше производительности суммы. Такого можно добиться, когда человек получает задачи, которые наиболее эффективно выполняет именно он. Например, один прекрасно шарит в серверных технологиях, другой любит и умеет делать GUI. Каждый из них делал бы проект в одиночку, скажем, три года, а вместе сделают за год (при правильном управлении), потому что каждый трудится в сфере своей максимальной компетенции.
Что значит «большего»? Тут или «большего», или «большого».
Имелось в виду «бо́льшего», я думаю =) И полужирным выделена не ударная гласная, а отличающаяся.
Мне сложновато это ещё до-осмыслить.

Добавлю лишь, что не всегда, но часто обработка Use Case «номером два» это последовательность вызовов «разделяемого» кода.
Т.е., да, методы для «точки входа в Use Case» получаются у него похожими, но выглядят они иначе, нежели те же методы «номера один».
В тех же методах «номера два» практически нет конструкции IF.
Лично я полностью поддерживаю «второго» и тоже стараюсь использовать именно такой подход.
С другой стороны, ему, видимо (если я правильно всё понял), не хватает навыка непосредственно перед правкой проверять, где вызывается метод, который он исправляет, чтобы учесть все варианты.

Эти два подхода могут быть объединены за счёт правильной параметризации обработчика. Т.е. обработчик может быть один и универсальный, но в нём все юзе-кейсы должны быть отражены в виде входных параметров. Это либо непосредственно параметры методов:

function f(a1, a2) ...

либо параметры вычисляемые в начале тела метода:

function f(obj) 
{
   a1= obj.is_special? 1 : 0;
   a2= obj.type == 'VIP' ? 'money' : 'time';
   ....
}


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

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

function onClick()
{
      ...
      if(textField1.text == 'Hello!' && button2.state == 'disabled'; ) { ... }
      ...
}


вместо

function onClick()
{
      testFormInput(textField1.text,  button2.state);
}
function testFormInput(text, state)
{
      ...
      if(text == 'Hello!' && state == 'disabled') { ... }
      ...
}



в результате: код хардкорно привязан к элементам интерфейса, и, не дай бог, надо поменять структуру формы. Точки входа данных всегда необходимо минимизировать по количеству и строго систематизировать, потому что именно входные данные — это то, что вносит хаос в выполнение нашей идеальной программы.
Вот смотрю на второй код и дикое желание не писать вообще функцию onClick, а где-то написать что-то вроде
$('#button').onclick = function() {testFormInput(textField1.text,  button2.state);}
//...
function testFormInput(text, state)
{
      ...
      if(text == 'Hello!' && state == 'disabled') { ... }
      ...
}

вместо того, чтобы где-то писать
$('#button').onclick = function() {testFormInput(textField1.text,  button2.state);}
//...
function onClick()
{
      testFormInput(textField1.text,  button2.state);
}
function testFormInput(text, state)
{
      ...
      if(text == 'Hello!' && state == 'disabled') { ... }
      ...
}


Имхо, читаемость резко повышается.
Ваше замечание, к сожалению, не по существу. В данном случае код совершенно абстрактный и подразумевается, что onClick — это некий обработчик нажатия на некую кнопку, т.е., фактически, имеется в виду то, что вы написали в своем первом примере.
Если бф я не встречал такое в реальном коде,, то не стал бы делать замечания.
К счастью, мы не о реальном коде говорим)
Во втором куске кода в первой строчке опечатка. Там, видимо, имелось, в виду onClick.
Угу, вот он зло копипаста :)
Наблюдение интересное, но имхо, вывод о причинах неправильный. Всё время работал с языками, подразумивающими строгую типизацию (более или менее — C++, C#, Obj-C), но использую подход 2го программиста, именно потому, что он позволяет избежать множества правок в том месте, где можно было бы обойтись одной, а так же кучи копипасты при появлении нового юрезкейса. И это считается хорошим подходом. А код первого программера, с высокой долей вероятности, я бы отправил на govnokod.ru
А код первого программера, с высокой долей вероятности, я бы отправил на govnokod.ru

И как вы работаете с другими программистами с такими заносами? Нормально всё? Отношения тёплые и комфортные?
Было бы правильнее назвать номер один Пашей, а номер два Машей, было бы нагляднее ))
Да простят меня милые дамы, но у них по статистике совсем другие проблемы. :)
Первый — Паша, второй — Ваша :D
При чем тут многопоточность? Тут просто подходы разные. первый любит передавать 100-500 параметров и обрабатывать магичиские ключи, второй создает отдельный метод на операцию, т.е. каждый метод атомарный и делает что то одно а не занимается всем подряд.
А многопоточность, языки со строгой типизаций тут не причем как по мне.
Подразумевался способ мышления, а не логика процессора.
мне кажется многопоточный способ мышления это не значит писать все в одном методе.
просто первый программист не думает о будущем. а второй думает. если есть общая логика, то лучше общую логику вынести в отдельный метод.
в первом случае не далеко до копипастов, когда сам зароется в своих флагах и условиях проверок что нужно выполнять а что нет.
Мне кажется, тут «многопоточность» — неподходящий термин. Скорее, вопрос в более структурированном и менее структурированном мышлении.
В общем я бы выдвинул такую идею:
Может быть 1й программист легко правит код 2го не потому, что 1й кодит лучше, а потому, что коде 1го сам чёрт ногу сломит.
Мне тоже поначалу так показалось.
Но сложность кода там в разумном смысле отходит на второй план, алгоритмы в общем-то простые.
Просто «номер два» вообще «не включается» в то, что обработчик разделён на несколько Use Case, потому, что сам так не пишет.
Если один метод работает с несколькими юз-кейсами, значит, это должно быть отражено в коде в виде условных переходов. И, судя по всему, разница между этими юз-кейсами «зашита» где-то в середине кода и хрен сразу разберешься в режимах работы.
А это означает, что ошибка «номера один» в том, что он не умеет наглядно показать в своём методе, все его режимы работы. А делается это довольно просто: все параметры, влияющие на режим работы, необходимо выносить в начало метода и там их подробно комментировать, тогда «второй» сможет легко и непринуждённо, прочитав список параметров оценить все юз-кейсы без необходимости лезть в интерфейс.
Да, об этом я вообще не подумал, т.к. сам не совсем программист. Похоже, что так оно и есть, т.к. они комментируют только входные параметры, и результат, но не логику.
    
/// <summary>
/// Form submit method
/// </summary>
/// <param name="form"> this form</param>
/// <param name="obj"> Object of parameters and flags </param>

Это xml-комментарий для Visual Studio, а не для вас :)
Эти коментарии IDE использует для того, чтобы потом выдать подсказку при автодополнении кода, а так же их используют инструменты для автоматической генерации сопровождающей документации.
Логику не надо комментировать, иначе комменты очень быстро отстанут от кода. Если код требует комментирования, это очень плохо, нужно выделять методы и заниматься прочим рефакторингом (то есть код будет выглядеть как у Shock в комменте выше).
Полностью не согласен.
Комментировать логику надо всегда и достаточно подробно, и при изменении логики надо менять комментарии. Естественно, писать что-то типа:
a = b; // скопируем параметр b в a
это тупо и является примером мусорного комментирования.
Комментарии должны отображать глобальный смысл происходящего:
  // вычислим дискриминант уравнения:
  D = b^2 - 4*a*c;
  // найдём корни:
  if(D>0) { .. } else { ... }


Комментарии — это описание работы алгоритма на человеческом языке.
Комментарии должны отображать глобальный смысл происходящего:
  // вычислим дискриминант уравнения:
  D = b^2 - 4*a*c;
  // найдём корни:
  if(D>0) { .. } else { ... }


Ну вот плохой пример. Комменты в нём не нужны. Намного лучше так, и не нужны никакие комменты:
var D = MathUtils.discriminant(a, b, c);
var roots = MathUtils.roots(D);
Во-первых, мало кому нужен дискриминант сам по себе. Во-вторых, в вашем примере придётся делать так:

var D = MathUtils.discriminant(a, b, c);
var roots = MathUtils.roots(a, b, D);


Соответственно, в общем случае правильнее было бы так:

var roots = MathUtils.roots(a, b, c);


А внутри уже то, что я писал выше.
Тогда в моём примере лучше всего так:
var discriminant = new Math.Discriminant(a, b, c);
var value = discriminant.value();
var roots = discriminant.roots();


Я ведь идею хотел только передать
Ну, это уже совсем что-то с чем-то :D
За такой бардак своему подчинённому шею бы намылил)))
Выделять дискриминант в отдельный объект — это жесть))

Гораздо лучше так:

v = new Vector(a, b, c);
roots = v.square_equation_roots();
Ну вы издеваетесь. Это ведь абстрактный пример.
Нет, не издеваюсь. Прошу прощения, если так выглядит.

Пример-то, конечно, относительно абстрактный, но мы разбираем необходимость комментариев именно на нём.

Вы пытаетесь мне доказать, что код должен быть самокомментирующимся (с чем я согласен!), но я утверждаю, что этого самокомментирования не достаточно, т.к. читабельность этого кода можно значительно повысить за счёт полноценных комментариев на человеческом языке.
Гораздо лучше так:
v = new Vector(a, b, c);
roots = v.square_equation_roots();

Ага, а реализовать square_equation_roots как-то так:
function square_equation_roots(){
  // вычислим дискриминант уравнения:
  D = this.b^2 - 4*this.a*this.c;
  // найдём корни:
  if(D>0) { .. } else { ... }
}

Ой, а не этот ли код мы пытались отрефакторить? :)
Лично я не пытался его рефакторить.
Речь вообще шла о том, какие должны быть комментарии в коде)
v = new Vector(a, b, c); roots = v.square_equation_roots();


Выглядит как г#@но, простите за прямоту. У меня наверное ООП/C# головного мозга в крайней стадии, но даже вариант с выделением дискриминанта в отдельную сущность — гораздо приятнее глазу. Хотя надо признать и там слегка перебор.

Вот геймдевелоперов часто видно издалека. У них (вас?) есть мощный навык в помойке говнокода видеть четкую структуру, и месиво из алгоритма, оптимизаций, хаков считать нормой. Признаю — структура нужна слабым, гений может править хаосом, это бесспорно, но мозги нужны недюжинные. Надо уважать право других программистов быть слабее вас. В конце концов чаще всего над конечным продуктом трудится больше 1го человека
Выглядит как г#@но, простите за прямоту.

Вот тут не понял аргументации, извините.

Надо уважать право других программистов быть слабее вас. В конце концов чаще всего над конечным продуктом трудится больше 1го человека.

Я уважаю право своих подчиненных на улучшение своих навыков и обычно не жалею времени, чтобы помочь им в этом нелегком пути. Если я вижу, что человек заинтересован в улучшении своего уровня, я ему помогу всеми доступными мне способами, а если вижу, что не заинтересован, то скорее всего мы с ним вскоре расстанемся. Конечно, это происходит, потому что я работаю с небольшой группой, в которой все время приходится решать новые задачи и искать новые решения для старых. Простые «рабочие лошадки», по 5 лет выполняющие одинаковые задачи, востребованы в больших и долгих проектах, но не у нас.

Вот геймдевелоперов часто видно издалека.

Я пока еще не стал бы называть себя геймдевелопером — в эту тему только начал погружаться. Прежде всего я — веб-девелопер.

месиво из алгоритма, оптимизаций, хаков считать нормой.

Я большой поклонник красивого кода и всегда стараюсь писать как можно более красиво, хотя, признаюсь, стараюсь не забывать про то, что «лучшее — враг хорошего», и разного рода заплатки, конечно же, периодически появляются в моём коде, например, когда мне говорят: «а вот для вот этого особого пользователя пусть в этом месте будет не 5, а 6».
Вот геймдевелоперов часто видно издалека. У них (вас?) есть мощный навык в помойке говнокода видеть четкую структуру, и месиво из алгоритма, оптимизаций, хаков считать нормой

Я — геймдевелопер и не считаю такое нормой. И у меня много коллег — приверженцы хорошего кода.
На мой взгляд, пример для самодокументирующегося кода совсем неподходящий. Будь это обычный flow control с ветвлениями — то да, надо было бы повышать уровень абстракции и выделять отдельные методы.
Но конкретно в алгоритмах комментарии вполне уместны, а дробление на методы может только мешать общему пониманию логики работы кода.
Это уже Java головного мозга — куча ничего не значащих однострочных методов с длинными названиями, да еще и внутри пространства имен. Я против выделения одноразовых методов только ради комментирования. Roots, кстати, потребует, помимо D, b и a на вход
При чём тут Джава? Я не кричал о паттернах, фабриках и тому подобному. Я выделил логический блок кода в функцию/класс. Это не Джава, это элементарная логика.
Код «MathUtils.discriminant(a, b, c);» никогда повторно использован не будет, выделен он только ради того, чтобы быть выделенным. Занимает (вместе с выделенным методом) он больше места, чем
//discriminant
var d = b*b — 4 * a * c;
и требует лишних телодвижений читающего код. До фабрик тут далеко, но и логики особой не видно, если, конечно, это не отвязанный от предметной области пример самодокументирования.
А по-моему, оптимально:
var discriminant = b*b — 4 * a * c;
ну не ссылку же на википедию давать или теорию квадратных уравнений описывать. Это всё же даже не вузовская программа, и даже не старших классов, емнип.
Длинные переменные в математике обычно не нужны
В математике просто не принято. Но я бы не отказался: помнится, иногда вскипала у меня голова от многочисленных дефайнов теты-дзетты-хи-фи-кси-пси.
Но у нас же не математика, а в программировании подход
var someEntity = ... ;

вроде как очевидно выигрывает перед
var s = ... ; // s is someEntity
В математике не просто так не принято, короткие обозначения облегчают символьный анализ:
var discriminant = coefficients[1]*coefficients[1] — 4 * coefficients[0] * coefficients[2];
var firstResult = (-coefficients[0]*coefficients[0] + Math.sqrt(discriminant)) / (4 * coefficients[0] * coefficients[2]);
Стало понятнее? Нет. И если в бухгалтерской программе длинные переменные — терпимо, то в математической — управление сложностью терпит крах. Если голова вскипает от дзетт, значит формулы сложные, и от длинных описаний проще они не станут, будут только давать иллюзию простоты. Нужно подымать бумагу, в ней будут дзетты, и пока разберешься, в долговременную память загрузится, что они означают
Голова вскипала не столько от сложности доказательств (ну не без этого, конечно), сколько от того, что в каждой теореме одни и те же буквы обозначали совсем разные сущности. А переключать контекст между теоремами приходилоь до того, как успевала заработать долговременная память (на сколько я помню, для неё 30 минут требуется).
Кстати, не будь в вашем коде ошибок, он бы выглядел вполне понятным. :)
Впрочем, обе крайности плохи, я за разумный компромисс.
Почему-то вспомнились замечательные константы в Cocoa для iOS:
NSPersistentStoreDidImportUbiquitousContentChangesNotification
MPMusicPlayerControllerNowPlayingItemDidChangeNotification

А ведь могли бы назвать как-нибудь так:
NSPerStDIUbiCntChN
MPMPlCtrNPlItChN

Но ведь не назвали.
Не знаю, почему вспомнились, речь не об этом.
Блин, я бы подумал, что второе — это хеш.
Вы приводите пример мусорного комментария.
Гораздо лучше делать так:
// Создадим наш главный рабочий объект
var someEntity = new Processor();

То, что это объект очевидно, но чтобы понять, что этот объект «самый главный» для данного фрагмента кода, если комментария нет, придется сначала прочитать код. А если есть — то картинка в голове возникает сразу.
Вы приводите пример мусорного комментария.
Гораздо лучше делать так:

// Создадим наш главный рабочий объект
var someEntity = new Processor();

То, что это объект очевидно, но чтобы понять, что этот объект «самый главный» для данного фрагмента кода, если комментария нет, придется сначала прочитать код. А если есть — то картинка в голове возникает сразу.
Главный рабочий объект? Серьезно? Меня такой комментарий поставил в ступор. Зачем мыслить такими категориями? Мне ни разу в жизни не приходилось разделять объекты по принципу рабочие/не рабочие, главные/второстепенные. В коде мы работаем с процессором? Тогда
var processor = new Processor();

Процессоров не один, а несколько? Тогда
var mainProcessor = new Processor();

И т.п. Подобный код идеально самодокументируется.
Не воспринимайте всё буквально. «Главный рабочий объект» — это шаблон описания объекта, а на самом деле там должна быть описана роль данного объекта в вычислениях. Объект назван «Processor» — всего лишь, чтобы отразить, что он что-то делает.
Название «mainProcessor» ничего не говорит о том, чем этот Processor будет заниматься, а именно это должно быть отражено в комментариях:
// Основной обработчик клиентских вызовов (для красных вариантов)
var redProcessor = new Processor();

Потому что дальше может, например, оказаться такой код:
// Вспомогательный обработчик клиентских вызовов (для синих вариантов)
var blueProcessor = new Processor();
// Вспомогательный обработчик клиентских вызовов (для зелёных вариантов)
var greenProcessor = new Processor();


И, пожалуйста, не доказывайте мне что вместо redProcessor можно написать main_red_client_recall_processor, потому что пример упрощенный, а в реальной жизни всё может оказаться сложнее.

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

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

Я как руководитель команды программистов утверждаю, что код без адекватных комментариев — ВСЕГДА становится проблемой, после того как автор данного кода уходит из проекта.
Складывается ощущение, что вы комментируете каждый чих. Предполагаю, что это всё-таки не так, поэтому прошу дать пример любого open-source проекта, который вы считаете хорошо прокомментированным.
На мой взгляд, адекватное комментирование — это комментирование интерфейсов (как использовать метод/класс, что он возвращает, какие исключения выбрасывает и т.п.) плюс комментарии сложных и неочевидных моментов реализации, где из кода нельзя понять необходимую информацию. Причем необходимость написать комментарий в реализации — первый признак того, что надо критически оценить код на предмет упрощения и рефакторинга.
Из моей практики, в «живом» усердно откомментированном коде всегда есть устаревшие комментарии, что является куда большей проблемой, нежели отсутствие комментариев вовсе.
Я же несколько раз упомянул, что комментарии должны пояснять смысл происходящего, а не дублировать код. Конечно же, я не комментирую каждый чих.
Я считаю, что довольно хорошо откомментированны, например, исходники Андроида, хотя лично на мой вкус в некоторых отдельных местах их всё-таки не хватает — бывает сложновато разобраться.
Хорошая практика подразумевает, что ты при изменении кода должен перечитать соответствующие комментарии и исправить их. Да, баги в комментах могут так же появляться, как и в коде, тем более что отдебажить комменты нельзя, но это вовсе не означает, что следует от них отказаться.
Например, если ты меняешь список параметров функции, то, будь добр, отрази эти изменения в комментах к ней.

Плотность, с которой лично я комментирую свой код зависит от таких параметров:
1. Если это разовая или очень срочная (но не очень сложная) задачка, то могу оставить почти без комментариев — только самые сложные места.
2. Если я пишу код, который в обозримом будущем буду поддерживать только я, то комментирую самые сложные места и смысл отдельных методов и объектов.
3. Если я работаю над кодом со своей командой, то стараюсь сделать так, чтобы у моих соратников было как можно меньше вопросов вида: «Что это тут за хитровыжопленная хрень? Я вообще не понимаю, как это работает!». Т.е. стараюсь, чтобы, если я ушел в отпуск, то без меня могли что-то дописать. В этом случае я могу в комментариях расписать какие-то архитектурные особенности и связи.
4. Если я работаю на аутсорс, то стараюсь сделать так, чтобы мой код мог разобрать любой адекватный программист, не матерясь в мой адрес на каждой строчке и не тратя по полчаса на 5 строк. Да, и еще, очень здорово, когда программисту не обязательно разбираться в предметной области, чтобы прочитать данную программу.

К сожалению, я не гений, и практика не всегда соответствует теории, но я по крайней мере стараюсь :)

Если предметная область не широко известна (или известна, но вводятся оригинальные решения) и отдельной документации к программе нет, то, имхо, нужно документировать и её. Где-то можно обойтись именованием как способом документирования того, что код делает, но далеко не всегда хотя бы из соображений, что идентификатор не должен занимать больше 80 символов (вернее чуть меньше), если принятым стандартом кодирования такова максимальная длина строки кода. Плюс часто нужно документировать почему код так себя должен вести.

В общем, нельзя рассматривать комментирование только как вещь в себе, или даже только в коде. Комментирование один из способов документирования проекта, наряду с именованием, проектной документацией, ТЗ, инструкциями пользователя и администратора и т. п. И даже при наличии обширной другой документации ссылки в коментах на неё не помешают типа «см. п. 17.12 ТЗ».
Если приблизить пример к реальной жизни, где вместо квадратных уравнений наверняка будет что-нибудь посложнее и специфичнее типа метода Рунге-Кутта или ещё чего, то ссылочка на википедию была бы очень кстати, как и подробные комментарии, что обозначают переменные a, b и c.
Я думаю, это человек просто образно выразился.
Все это можно вынести непосредственно в код а-ля Discriminant = b^2 — 4*a*c; (хотя вообще я бы не стал тут комментировать вообще ничего, в комментарии должна быть только на соответствующий пейпер, как будто слово «дискриминант» будет говорить что-то тому, кто не знает о квадратных уравнениях ничего).
Лично я комменчу ветки ветвления непосредственно в коде, причем только те, которые нет смысла выделять в отдельный метод. Описывать реализацию не в теле метода — зло, потому что разойдуться комменты от кода обязательно, и никакими дисциплинарными методами это не решить.
Дело в том, что в программах далеко не все алгоритмы относятся к стандартным (типа квадратных корней), а значит, читатель кода (особенно, если он не автор) во многих случая окажется неподготовленным. Комментарии на естественном языке гораздо читабельнее программного кода, поэтому чтение и понимание хорошо откомментированного кода происходит гораздо быстрее, чем (любого!) неоткомментированного.
А первый случайно вот так: govnokod.ru/10315 не делает?

Или, не дай бог, вот так: govnokod.ru/9618
Такое с 1й попытки не разгребсти )
Первый в описанных методах часто делает так:
if(){}else if{}else if{}else if{}

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

Я не уверен, но вроде бы else if это не совсем из JS, т.е. оно работает, но как-то «по другому».
> Первый в описанных методах часто делает так:
govnokod.ru/9614
Вот так? :)
Извините, просто у меня много (ну очень много) претензий к программисту, использовавшему 1й подход :)

Как мне кажется, else-if'ы имеют смысл только при обработке перечисляемых типов (enum), указывающих на тип сущности. И то, неплохо бы это вынести в виртуальные функции или хотя бы ограничиться диспетчеризацией вызовы с помощью switch-case

А вообще, имхо, js здесь не причём, конструкцию else-if можно соорудить где угодно.

govnokod.ru/9614

Похоже, но не так много, штуки три-четыре за раз, не десяток.
На десяток switch ставят, но свичей в их собственном коде нет вообще, наверное это как-то связано с производительностью.
В общем, вы выяснили, что первый программист пишет код, который в долгосрочной перспективе вообще не будет читаемым и поддерживаемым хоть в какой-то степени. Про скорость работы тоже сказать нечего, поскольку ветвления всякие бывают, и не всегда их на else-if'ах вывезти можно.
Вы бы дали им обоим прочитать про паттерны «Стратегия» или «Цепочка обязанностей», они бы умнее стали, код, глядишь, лучше выглядел бы. А вы его минифицируйте, если так за количество строк радеете.
Оба подхода имеют право на существование.
Первый для программирования в системах с ограниченными ресурсами — микроконтроллеры, медленные каналы для загрузки кода, мало памяти для хранения кода и т.д.
Второй для задач, где оптимизация кода менее важна по сравнению с читаемостью кода и скоростью разработки.
Причем, оптимизированный код всегда будет дороже как в написании так и в сопровождении.
Если для автора более важен фактор стоимости кода, то ему следует посоветовать программисту первого типа перейти в разработку софта например для микроконтроллеров (он скорее всего там даже больше будет зарабатывать), а вместо него нанять еще одного программиста второго типа.
Имхо тут оба неправы. Первый думает в одной плоскости(JavaScript'ер) и не пытается включить соображалку, а второй плодит адов код, как он творил раньше.
Все эти if'ы else'ы и т.п. никуда по сути не денутся. Ну завуалируются немного. Конечно это не повод писать монстр-методы. Это лишь повод немного призадуматься о том, что истина где-то по-середине.
Если это вход в точки обработки событий, то денутся. По сути что происходит, если я правильно разобрался — есть три контрола, событию onclick которых «насильно» вешается один обработчик, который разбирается, от которого контрола пришло событие и выбирается соотвествующая логика. Если повесить три разных хэндлера, то разбираться просто не понадобится. Эти if-else если и будут выполняться, то где-то в коде браузера (который на C/C++ грубо говоря), разработчику его в JS писать не нужно будет.
Sign up to leave a comment.

Articles

Change theme settings