Чистый читаемый код против быстрого трудно читаемого кода. Когда пересекать черту?

67

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

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

Когда это нормально, чтобы пересечь эту линию?

Кен Кокрейн
источник
69
Вы ответили на свой вопрос, вы пересекаете черту, когда вам нужно пересечь черту
gnibbler
6
Кроме того, ваш «грязный код» может работать так же быстро, как и «чистый код» на оборудовании через 6 месяцев. Не идите за борт, как Windows, хотя. :)
Матин Улхак
21
Существует значительная разница между сложным для понимания алгоритмом и сложным для понимания кодом. Иногда алгоритм, который вам нужно реализовать, сложен, и код обязательно будет сбивать с толку просто потому, что он выражает сложную идею. Но если сам код является трудным моментом, то код должен быть исправлен.
Tylerl
8
Во многих случаях умный компилятор / интерпретатор может оптимизировать чистый читаемый код, чтобы он имел ту же производительность, что и «уродливый» код. Так что оправданий мало, если профилирование не говорит иначе.
Дэн Дипломат
1
Когда речь заходит о компиляторах в наши дни, ваш уродливый код, скорее всего, будет таким же, как ваш чистый код (при условии, что вы не делаете действительно странных вещей). Особенно в .NET, это не то же самое, что C ++ / MFC дни, когда определение ваших переменных будет влиять на производительность. Написать обслуживаемый код. некоторый код просто окажется сложным, но это не значит, что он ужасен.
ДастинДэвис

Ответы:

118

Вы пересекаете черту, когда

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

Вот реальный пример: экспериментальная система, на которой я работаю, генерировала данные слишком медленно, занимая более 9 часов за цикл и используя только 40% ЦП. Вместо того, чтобы перепутать код, я переместил все временные файлы в файловую систему в памяти. Добавлено 8 новых строк некрасивого кода, и теперь загрузка ЦП превышает 98%. Задача решена; безобразия не требуется.

Норман Рэмси
источник
2
Вы также убедитесь, что сохраняете исходный, более медленный и чистый код как справочную реализацию, так чтобы использовать его в случае изменения аппаратного обеспечения и более быстрого, более хакерского кода, который больше не работает.
Пол Р
4
@PaulR Как вы храните этот код? В форме комментариев? Это неправильно, imo - комментарии устарели, никто их не читает, и лично, если я вижу закомментированный код, я обычно удаляю его - для этого нужен контроль версий. Комментарий к методу, объясняющему, что он делает лучше, IMO.
Евгений
5
@ Евгений: я обычно сохраняю исходную версию подпрограммы с именем fooи переименовываю ее foo_ref- обычно она находится выше fooв исходном файле. В моем тестовом жгуте я призываю fooи foo_refдля проверки и измерения относительной производительности.
Пол Р
5
@Paul, если вы делаете это, было бы хорошей идеей провалить тест, если оптимизированная версия когда-либо медленнее, чем функция ref. это может произойти, если предположения, которые вы сделали, чтобы сделать это быстрее, больше не верны.
user1852503
58

Это ложная дихотомия. Вы можете сделать код быстрым и простым в обслуживании.

То, как вы это делаете, - пишите это чисто, особенно с максимально простой структурой данных.

Затем вы узнаете, где время истощает (запустив его, после того , как вы его написали, а не раньше), и исправьте их один за другим. (Вот пример.)

Добавлено: мы всегда слышим о компромиссах, верно, таких как компромисс между временем и памятью или компромисс между скоростью и ремонтопригодностью? Хотя такие кривые вполне могут существовать, не следует предполагать, что какая-либо конкретная программа находится на кривой или даже где-то рядом с ней.

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

По моему опыту, именно здесь начинаются многие программы.

Майк Данлавей
источник
Я согласен. Действительно быстрый код, который не является чистым, со временем станет медленнее, поскольку вы не сможете его должным образом изменить.
edA-qa mort-ora-y
22
Я не согласен, что это ложная дихотомия; ИМО, есть сценарии, особенно в коде библиотеки (не столько в коде приложения ), где разделение вполне реально. Смотрите мой ответ для более.
Марк Гравелл
1
Марк, вы можете ссылаться на ответы в комментариях с помощью ссылки "URL". programmers.stackexchange.com/questions/89620/...
Мы все нашли время, когда нам нужно попытаться сделать код быстрее. Но после экспериментов с профилировщиком, чтобы найти лучшее решение (если код становится уродливым), это не означает, что код должен оставаться безобразным. Речь идет о поиске наилучшего решения, которое на первый взгляд может показаться неочевидным, но, как только оно найдено, его обычно можно правильно кодировать Таким образом, я считаю, что это ложная дихотомия и просто повод не убирать свою комнату после того, как вы повеселились со своими игрушками. Я говорю, смирись и убери свою комнату.
Мартин Йорк
Я не согласен с тем, что это ложная дихотомия. Поскольку я много работаю с графикой, самый очевидный пример для меня - плотные графические циклы: я не знаю, как часто это все еще делается, но раньше игровые движки, написанные на C, часто использовали Assembly для визуализации ядра. петли, чтобы выжать каждую последнюю каплю скорости. Это также заставляет меня думать о ситуациях, когда вы программируете на Python, но используете модули, написанные на C ++. «Трудно читать» всегда относительно; всякий раз, когда вы переходите на язык более низкого уровня для скорости, этот код труднее читать, чем остальные.
Джокинг
31

В моем существовании OSS я выполняю большую библиотечную работу, направленную на повышение производительности, которая тесно связана со структурой данных вызывающей стороны (т. Е. Внешней по отношению к библиотеке), при этом (по замыслу) нет мандата на входящие типы. Здесь лучший способ сделать это перманентным - это метапрограммирование, которое (поскольку я нахожусь в .NET-land) означает IL-emit. Это некрасивый, уродливый код, но очень быстрый.

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

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

Теперь код приложения немного отличается, поскольку именно здесь «обычные» (здравомыслящие) разработчики тратят большую часть своего совместного / профессионального времени; цели и ожидания каждого (ИМО) немного отличаются.

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

Марк Гравелл
источник
4
Тот факт, что код некрасив, не означает, что он должен быть неуправляемым. Комментарии и отступы бесплатны, и некрасивый код обычно может быть заключен в управляемую сущность (класс, модуль, пакет, функцию, в зависимости от языка). Код может быть таким же безобразным, но тогда, по крайней мере, люди смогут судить о влиянии изменений, которые они собираются внести в него.
tdammers
6
@tdammers, и я стараюсь делать это насколько это возможно; но это немного похоже на нанесение помады на свинью.
Марк Гравелл
1
Что ж, возможно, следует различать некрасивый синтаксис и некрасивые алгоритмы - некрасивые алгоритмы иногда необходимы, но некрасивый синтаксис - вообще непростительная IMO.
tdammers
4
Уродливый синтаксис @IMO довольно неизбежен, если то, что вы делаете, это по своей природе несколько уровней абстракции ниже обычного уровня языка.
Марк Гравелл
1
@ Марк ... это интересно. Моей первой реакцией на некрасивую мета / аннотацию было подозрение, что конкретный язык / форма листа не способствует мета-кодированию, а не какой-то основной закон, связывающий их. То, что заставило меня поверить, было примером прогрессивных метауровней в математике, оканчивающихся теорией множеств, выражение которых едва ли более уродливо, чем алгебра или даже конкретная арифметика. Но тогда сет-нотация, вероятно, совсем другой язык, и каждый уровень абстракции ниже имеет свой собственный язык ...
исследовать
26

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

Джоэл Рейн
источник
3
А что значит "значительный"?
Ладья
2
@ hotpaw2: это разумный ответ - он предполагает, что разработчики, по крайней мере, несколько конкурируют. В противном случае да, использование чего-то более быстрого, чем пузырьковая сортировка (обычно) хорошая идея. Но слишком часто кто-то (чтобы продолжать сортировку) менял быструю перестановку на heapsort с разницей в 1%, только чтобы увидеть, как кто-то другой обменяет ее через шесть месяцев по той же причине.
1
Там нет и не причина , чтобы не-чистый код. Если вы не можете сделать свой эффективный код чистым и простым в обслуживании, вы делаете что-то не так.
edA-qa mort-ora-y
2
@SF. - клиент всегда найдет его слишком медленным, если это может быть быстрее. Он не заботится о «чистоте» кода.
Ладья
1
@Rook: Клиент может найти (тривиальный) код интерфейса слишком медленным. Некоторые довольно простые психологические трюки улучшают пользовательский опыт без фактического ускорения кода - откладывайте события кнопок до фоновой процедуры вместо выполнения действий на лету, показа индикатора выполнения или чего-то в этом роде, задавайте незначительные вопросы, пока действие выполняется в фоновом режиме ... это когда этого недостаточно, вы можете рассмотреть фактическую оптимизацию.
SF.
13

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

Написание «грязного» кода в попытке ускорить его, возможно, неразумно, поскольку вы точно не знаете, что ваши изменения действительно что-то улучшают. Кнут говорит:

«Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла . Но мы не должны упускать наши возможности в эти критические 3%. Хороший программист не будет доволен такими самоуспокоенностью». рассуждая, ему будет разумно внимательно посмотреть на критический код, но только после того, как этот код будет идентифицирован ».

Другими словами, сначала напишите чистый код. Затем профилируйте полученную программу и посмотрите, является ли этот сегмент узким местом в производительности. Если это так, оптимизируйте этот раздел по мере необходимости и обязательно включите множество комментариев к документации (возможно, включая исходный код) для объяснения оптимизации. Затем профилируйте результат, чтобы убедиться, что вы действительно внесли улучшения.

tylerl
источник
10

Поскольку вопрос говорит «быстро трудно читаемый код», простой ответ никогда не бывает. Нет никакого оправдания написанию кода, который трудно читать. Почему? Две причины.

  1. Что произойдет, если вас сегодня вечером по дороге домой будет сбить автобус? Или (более оптимистично и более типично) сняли этот проект и переназначили что-то еще? Небольшая выгода, которую вы себе представляете с помощью своего запутанного беспорядка кода, полностью перевешивается тем фактом, что никто другой не может этого понять . Риск, который это представляет для программных проектов, трудно переоценить. Я работал однажды с крупной АТСпроизводитель (если вы работаете в офисе, возможно, у вас на столе один из их телефонов). Их менеджер проекта однажды сказал мне, что их основной продукт - проприетарное программное обеспечение, которое превратило стандартную коробку Linux в полнофункциональную телефонную станцию, - был известен внутри компании как «блоб». Никто не понял это больше. Каждый раз, когда они реализовали новую функцию. они нажимали на компиляцию, затем отступали, закрывали глаза, считали до двадцати, а затем заглядывали сквозь пальцы, чтобы увидеть, работает ли это. Ни одному бизнесу не нужен основной продукт, который он больше не контролирует, но это пугающе распространенный сценарий.
  2. Но мне нужно оптимизировать! Итак, вы следовали всем отличным советам в других ответах на этот вопрос: ваш код не проходит тесты производительности, вы тщательно его профилировали, выявляли узкие места, находили решение ... и он собирается включать в себя немного битый поворот . Хорошо: теперь продолжайте и оптимизируйте. Но вот секрет (и вы можете сесть за это): оптимизация и уменьшение размера исходного кода не одно и то же, Комментарии, пробелы, квадратные скобки и значимые имена переменных - все это огромные средства для удобочитаемости, которые абсолютно ничего не стоят, потому что компилятор их выбросит. (Или, если вы пишете не скомпилированный язык, такой как JavaScript - и да, есть очень веские причины для оптимизации JavaScript - они могут быть обработаны компрессором .) Длинные строки сжатого , минималистского кода (как у muntoo размещенный здесь ) не имеет ничего общего с оптимизацией: это программист, пытающийся показать, насколько они умны, упаковывая как можно больше кода в как можно меньше символов. Это не умно, это глупо. По-настоящему умный программист - это тот, кто может ясно донести свои идеи до других.
Марк Уитакер
источник
2
Я не могу согласиться с тем, что ответ «никогда». Некоторые алгоритмы по своей природе очень трудно понять и / или эффективно реализовать. Чтение кода, независимо от количества комментариев, может быть очень сложным процессом.
Рекс Керр
4

Когда это одноразовый код. Я имею в виду буквально: когда вы пишете сценарий для выполнения одноразового вычисления или задачи и знаете с такой уверенностью, что вам никогда не придется выполнять это действие снова, что вы можете «rm source-file» без колебаний, тогда вы можете выбрать уродливый маршрут.

В противном случае это ложная дихотомия - если вы думаете, что вам нужно сделать уродливым, чтобы сделать это быстрее, вы делаете это неправильно. (Или ваши принципы о том, что такое хороший код, нуждаются в пересмотре. Использование goto на самом деле довольно элегантно, когда это правильное решение проблемы. Однако редко бывает.)

maaku
источник
5
Нет такого понятия, как одноразовый код. Если бы у меня был пенни за каждый раз, когда «выбрасываемый код» превращался в продукт, потому что «он работает, у нас нет времени переписывать его», я бы стал миллионером. Каждая строка кода, которую вы пишете, должна быть написана так, чтобы другой компетентный программист мог забрать ее завтра после того, как вас ударит молния сегодня вечером. В противном случае не пишите это.
Марк Уитакер
Я не согласен, что это ложная дихотомия; ИМО, есть сценарии, особенно в коде библиотеки (не столько в коде приложения), где разделение вполне реально. Смотрите мой ответ для более.
Марк Гравелл
@ mark, если «другой компетентный программист» действительно компетентен, то и выбрасываемый код не должен быть проблемой :)
@ Марк - Легко. Просто напишите одноразовый код, чтобы он не прошел производственное тестирование, возможно, каким-то нефиксированным способом.
hotpaw2
@ Марк, если ваш «одноразовый код» превращает его в рабочий продукт, то это не одноразовый код. Обратите внимание, что я потратил время в своем ответе, чтобы уточнить, что я говорю о коде, который буквально выбрасывается: т.е. удаляется после первого использования. В противном случае я согласен с вашим мнением и сказал столько же в моем ответе.
Мааку
3

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

Люди до сих пор делают скрученные SSE / NEON / и т. Д. сборка, чтобы попытаться побить программное обеспечение конкурента на популярном в этом году чипе процессора.

hotpaw2
источник
Хорошая бизнес-перспектива, иногда программистам нужно выходить за рамки чисто технического.
this.josh
3

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

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

Carlos
источник
0

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

Естественно, что даже самый трудный для поддержки код, который можно себе представить, независимо от того, насколько легко или сложно его читать, не представляет проблемы, если нет причин для его изменения, просто используйте его. И можно добиться такого качества, особенно для низкоуровневого системного кода, где производительность часто имеет наибольшее значение. У меня есть C-код, который я до сих пор регулярно использую, который не менялся с конца 80-х годов. Это не нужно было менять с тех пор. Код бесполезен, написан в битые дни, и я его почти не понимаю. Тем не менее, это все еще применимо сегодня, и мне не нужно понимать его реализацию, чтобы извлечь из него много пользы.

Тщательное написание тестов - один из способов улучшить стабильность. Еще одна развязка. Если ваш код не зависит ни от чего другого, единственная причина, по которой он должен измениться, - это необходимость его изменения. Иногда незначительное количество дублирующегося кода может служить механизмом развязки для существенного улучшения стабильности, что делает его достойным компромиссом, если взамен вы получите код, который теперь полностью независим от всего остального. Теперь этот код неуязвим для изменений во внешнем мире. Между тем код, который зависит от 10 различных внешних библиотек, имеет в 10 раз больше причин для его изменения в будущем.

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

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

Стабильность должна быть вашей главной целью всякий раз, когда вы стремитесь написать код, который неизбежно будет трудно изменить, например, распараллеленный код SIMD, который был микротонизирован до смерти. Вы противодействуете сложности обслуживания кода, максимально увеличивая вероятность того, что вам не придется менять код, и, следовательно, вам не придется поддерживать его в будущем. Это сводит затраты на обслуживание к нулю независимо от того, насколько сложен в обслуживании код.


источник