Comments 17
Всё так. Я имею ввиду "список нелепых догм". Но всё есть одно замечание:
читаемость кода является показателем его качества
Качество понятие растяжимое. Здесь присутствует и решение поставленной задачи, и быстродействие. и расход памятм, не обойтись без пресловутой безопасности и т.д. и т.п.
Но вот читаемость кода это очень полезная сторона кода. Сам страдаю тем, что в ходе разработке пнаделаешь заплаток, а потом то ли лень, то ли жалко от низ избавиться.
Я бы убрал из этого списка нелепых догм читаемость. Красивый код дорогого стоит. Да, а eval мне нравится, я его частенько использую.
Красивый код дорогого стоит.
С этим никто не спорит. Просто красота не всегда достижима (алгоритмы), не всегда ценна (библиотечный код: API > читаемости) и даже не всегда нужна (одноразовый код).
Читаемый код лучше нечитаемого, при прочих равных. Тут без вопросов. Но слишком часто за красоту приходится расплачиваться.
Простой, но ёмкий пример: в иммутабельном языке в критичном месте можно сделать красивую и понятную трансформацию через несколько мапов, фильтров и чанков, а можно сделать один reduce
со сложным аккумулятором. Кроме очевидно ограниченного тремя элементами ввода — нечитаемый редьюс всегда лучше.
Красота, как и качество, многогранна. "Некрасивых женщин не бывает". Так что соглашусь с вами.
Добавлю, что так же читаемость не может говорить о качестве кода и уровне разработчика, если ты не знаешь в каких он был условиях и рамках.
Пример: была давно статья про киш-девелоперов, в которой описывалось, как в Фортране при помощи отрицательной индексации и т.д. делались хаки в ОС спутников НАСА, что бы делать обновления и патчи, которые не были предусмотрены до этого.
Ясно, что в условиях офиса - это жесткие костыли, которые "не приветствуются". Но иногда разработчики находятся в таких рамках, что это становится уже Кунг-Фу высшего класса.
Но просто смотря на код этого сказать нельзя без контекста.
П.С. Но стремиться к красоте нужно всегда. Просто в рамках доступного ресурса и рациональности его расходования.
Длинные функции действительно не очень хороши, но как догму это, конечно, применять не надо. А в остальном полностью согласен со списком нелепых догм.
Как-то мне довелось написать программу с двойным уровнем eval (программа строит исходный код второго уровня, который строит исходный код третьего уровня, который решает искомую задачу). В принципе, аналогичным делом занимается любой компилятор в байткод, только промежуточные языки у него разные. В науке это называется семантикой высших порядков (в данном случае третьего).
Длинные функции действительно не очень хороши […]
Если нет нарушений SRP, то одна длинная функция всегда лучше нескольких коротких. Книги не случайно (кроме Кортасара и отчасти Паланика) пишут сверху вниз, не заставляя читателя скакать туда-сюда глазами в каждой главке.
Эту аналогию можно повернуть по-другому. Книги не случайно (кроме Толстого) пишут короткими предложениями. Человеческий ум не приспособлен думать длинные мысли. А если функция реализует понятную абстракцию (то есть имеет чёткую денотационную семантику), то и скакать глазами в её реализацию от её использования не нужно.
скакать глазами в её реализацию от её использования не нужно
Я этот аргумент слышал триста раз (он с отрывом — самый популярный), и поэтому я его уже ненавижу.
Если исключить чтение чужого кода ради расширения собственного кругозора и в качестве снотворного перед сном, то чужой код люди читают тогда, когда с ним что-то не так. Ваш же аргумент про денотационную семантику срабатывает, ни с того ни с сего никто не лезет читать реализацию инверсии списка в стандартной библиотеке, или получение из базы данных пользователя. Вплоть до того момента, когда инверсия не инвертирует, а пользователь не достаётся.
И вот тут денотационная семантика уже не поможет. Надо будет понять, почему функция getUser
возвращает продукт — а для этого придется когнитивно напрячься, и загрузить её (всю!) в оперативную память (можно еще отладчиком пройтись, но если выявить ошибку помогает отладчик — это задача для стажера).
И здравствуй, как её там, дьяволицу, цикломатическая сложность, которая для прыг-скоков туда-сюда в сто раз больше, чем для вложенных ифов (которых тоже нужно избегать).
Читать чужой код ради поиска в нём ошибок – занятие, безусловно, почтенное, но обычно далеко не самое частое в жизни программиста. Поэтому странно руководствоваться им в качестве основного критерия для написания кода вообще.
Длинную функцию сложнее правильно написать, чем композицию коротких.
Лично я вообще предпочитаю методику снизу-вверх, и стараюсь не двигаться на высокий уровень абстракций, пока не разберусь с низким.
не самое частое в жизни программиста
А как вы ошибки чините?
Длинную функцию сложнее правильно написать, чем композицию коротких.
Почему это?
А как вы ошибки чините?
А я чужие ошибки редко чиню. Да и свои-то в общем чиню меньше, чем думаю над новым кодом. Обычно 90% отладки приходится на этап первоначального написания.
Длинную функцию сложнее правильно написать, чем композицию коротких.
Почему это?
Именно потому, что длинную мысль сложнее правильно сформулировать.
Каждый if увеличивает комбинаторную сложность вдвое.
90% отладки приходится на этап первоначального написания
Ну да. Но я думал, что разговор идёт о ситуации, когда мы уже зачем-то читаем код. Если мы обсуждаем написание — мне лично длинные предложения строить даже проще, чем короткие. Возможно я — Лев Толстой (только моему психиатру не рассказывайте).
Каждый
if
увеличивает комбинаторную сложность вдвое.
А вызов каждой дополнительной функции — уменьшает, что ли?
А вызов функции не меняет.
Сравните сложность выражения
a = min(x,y,z)/max(x,y,z)
и того же самого на условных операторах (через которые, допустим, мы написали и наши max и min).
Хитро вы выбрали функции, уже вынесенные в стандартную библиотеку. Но ладно. Чуть усложним задачу: есть реализация, выяснено, что в ней затык по производительности, надо оптимизировать. Вот мой код (неудачный):
[min | tail] = Enum.sort([x,y,z])
[max | _] = Enum.reverse(tail)
a = min / max
Вот, как я его перепишу:
{min, max} =
Enum.reduce([x,y,z], {nil, nil}, fn
v, {nil, nil} -> {v, v}
v, {min, max} when v < min -> {v, max}
v, {min, max} when v > max -> {min, v}
end)
a = min / max
Что станете делать вы, глядя на a = min(x,y,z)/max(x,y,z)?
Хитро вы выбрали функции, уже вынесенные в стандартную библиотеку.
Просто чтобы не вдаваться в объяснение денотата. Но мне-то денотат любой функции (стандартной или моей собственной) понятен, коль скоро я её использую.
выяснено, что в ней затык по производительности, надо оптимизировать
Вот когда надо будет оптимизировать, тогда и надо разбираться.
Сейчас мне кажется, что, возможно, разность взглядов вызвана тем, что я практически не пишу прикладной код — преимущественно библиотеки общего назначения. Большинство — в режиме «написал, отладил, выкатил, забыл».
Я просто не в состоянии помнить весь код, когда-либо написанный мной (я не смогу по памяти назвать даже проекты, в которые контрибьютил, их десятки, если не сотни, и код многих из них — я видел буквально пару часов от момента клонирования и поиска места, вызывающего проблему, до PR с тестами и бенчмарками). Я очень часто работаю с кодом, которого до того в глаза не видел, и спустя сутки уже не вспомню.
Поэтому мне проще иметь текст перед глазами. Если код изборожден вдоль и поперек — наверное скакать по нему туда-сюда — не проблема.
Песочница своими руками