В каких случаях меньше кода, а не лучше? [закрыто]


55

В последнее время я переработал некоторый код на работе, и я подумал, что хорошо поработал. Я опустил 980 строк кода до 450 и сократил вдвое количество классов.

Показывая это моим коллегам, некоторые не соглашались, что это улучшение.

Они сказали - «меньше строк кода не обязательно лучше»

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

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


145
Размер кода измеряется по времени, которое вам нужно прочитать и понять, а не по строкам или количеству символов.
Берги

13
Ваш вопрос, как написано, категорически слишком широк. Рекомендую написать новое о конкретных изменениях, которые вы сделали вместо этого.
jpmc26

8
Рассмотрим быстрый алгоритм обратного квадратного корня . Реализация полного метода Ньютона с правильным именованием переменных будет намного понятнее и намного проще для чтения, даже если он, вероятно, будет содержать больше строк кода. (Обратите внимание, что в данном конкретном случае использование умного кода было оправдано соображениями перфорации).
Мацей Пехотка

65
Существует целый сайт обмена стеками, посвященный ответу на ваш вопрос: codegolf.stackexchange.com . :)
Федерико Полони

Ответы:


123

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

Детскую историю из 980 строк легче читать, чем физическую диссертацию из 450 строк.

Есть много атрибутов, которые определяют качество вашего кода. Некоторые просто вычисляются, такие как цикломатическая сложность и сложность Холстеда . Другие более свободно определены, такие как сплоченность , читаемость, понятность, расширяемость, надежность, корректность, самодокументирование, чистота, тестируемость и многое другое.

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

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

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


1
@PiersyP просто к сведению, одно из руководств, которые меня учили о хорошем рефакторинге, заключается в том, что цикломатическая сложность должна быть уменьшена до квадратного корня по сравнению с тем, что было изначально.
М. А. Ханин

4
@PiersyP также, я не говорю, что ваш код хуже или лучше, чем он был. Как посторонний я не могу сказать. Возможно также, что ваши коллеги слишком консервативны и боятся ваших изменений просто потому, что не предприняли усилий, необходимых для их проверки и проверки. Вот почему я предложил вам обратиться к ним за дополнительными отзывами.
М. А. Ханин

6
Хорошая работа, ребята - вы установили, что где-то есть «правильный» вес (точное число может отличаться). Даже в оригинальных сообщениях @Neil написано «OVERweight», а не просто «чем тяжелее человек», и это потому, что есть такая приятная точка, как и в программировании. Добавление кода, превышающего этот «правильный размер», является просто беспорядком, а удаление строк ниже этой точки просто жертвует пониманием ради краткости. Зная, где именно эта точка ... это трудная часть.
AC

1
То, что это не нужно, не означает, что оно не имеет ценности.
Крис Волерт

1
@Neil В целом вы правы, но когда-либо неуловимый «баланс», на который вы ссылаетесь, является чем-то вроде мифа, объективно говоря. У всех разные представления о том, что такое «хороший баланс». Очевидно, OP думал, что он сделал что-то хорошее, а его коллеги - нет, но я уверен, что все они думали, что у них был «правильный баланс», когда они писали код.
code_dredd

35

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

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

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


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

17

На ум приходит цитата, которую часто приписывают Альберту Эйнштейну:

Сделайте все как можно проще, но не проще.

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

Такие языки, как Java и Pascal, печально известны своей многословностью. Люди часто указывают на определенные синтаксические элементы и насмешливо говорят, что «они просто существуют, чтобы упростить работу компилятора». Это более или менее верно, за исключением «справедливой» части. Чем более явной является информация, тем легче читать и понимать код не только компилятором, но и человеком.

Если я скажу var x = 2 + 2;, сразу видно, что xэто целое число. Но если я скажу var foo = value.Response;, гораздо менее ясно, что fooпредставляет или каковы его свойства и возможности. Даже если компилятор может легко сделать вывод, он прилагает гораздо больше познавательных усилий к человеку.

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


7
varпример не является особенно хорошим упрощения , так как большая часть времени на чтение и понимание кода включает в себя выяснение поведения на определенном уровне абстракции, поэтому зная фактические типы специфических переменных , как правило, ничего не меняет (это только помогает понять нижние абстракции). Лучшим примером может быть несколько строк простого кода, сжатых в одно замысловатое утверждение - например, if ((x = Foo()) != (y = Bar()) && CheckResult(x, y)) требуется время, чтобы понять, что за типы, xили yне помогает ни в малейшей степени.
Бен Коттрелл

15

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

Но давайте посмотрим с другой стороны. Мы предполагаем, что новый код будет рассматриваться как превосходящий большинству опытных программистов, которые видят 2 части кода, не имея дополнительных знаний о культуре компании, кодовой базе или дорожной карте. Даже тогда есть много причин возражать против нового кода. Для краткости я буду называть «Люди, критикующие новый код» Pecritenc :

  • Стабильность. Если было известно, что старый код стабилен, стабильность нового кода неизвестна. Прежде чем использовать новый код, его еще нужно протестировать. Если по какой-либо причине правильное тестирование недоступно, изменение является довольно большой проблемой. Даже если тестирование доступно, Pecritenc может подумать, что усилия не стоят (незначительного) улучшения кода.
  • Производительность / масштабирование. Старый код, возможно, масштабировался лучше, и Pecritenc предполагает, что производительность станет проблемой в будущем, так как клиенты и функции скоро накапливаются.
  • Расширяемость. Старый код мог легко представить некоторые функции, которые, как предполагает Pecritenc, будут добавлены в ближайшее время *.
  • Дружественные. В старом коде могут быть повторно использованы шаблоны, которые используются в 5 других местах кодовой базы компании. В то же время в новом коде используется причудливый шаблон, о котором на данный момент слышала только половина компании.
  • Помада на свинье. Pecritenc может подумать, что старый и новый код - мусор или нерелевантность, что делает любое сравнение между ними бессмысленным.
  • Pride. Возможно, Pecritenc был первоначальным автором кода и ему не нравятся люди, которые вносят огромные изменения в его код. Он может даже увидеть улучшения как легкое оскорбление, потому что они подразумевают, что он должен был сделать лучше.

4
+1 за «Pecritenc», и очень хорошее резюме разумных возражений, которые должны быть предварительно рассмотрены перед префакторингом.

1
И +1 для «расширяемости» - я думал, что в исходном коде могли быть функции или классы, которые предназначались для использования в будущем проекте, поэтому абстракции могли показаться избыточными или ненужными, но только в контексте одной программы.
Даррен Рингер

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

@nocomprende Любую причину, по которой вы использовали разумное, заранее продуманное и префакторинг? Способ похож на Pecritenc, может быть?
Milind R

@MilindR Возможно предвзятое мнение, пристрастие или, возможно, личные предпочтения? Или, может быть, просто без причины, космическое слияние кофакторов, запутанные заговорщические условия. Понятия не имею, правда. Как насчет тебя?

1

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

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

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

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


3
"Дедушка"! хо хо! Просто следите за классами Адама и Евы. (И Бог класс конечно) До этого было без форм и пустот.

1

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

Так,

enum OPTION1 { OPTION1_OFF, OPTION1_ON };
enum OPTION2 { OPTION2_OFF, OPTION2_ON };

void doSomething(OPTION1, OPTION2);

намного более многословен, чем

void doSomething(bool, bool);

Тем не мение,

doSomething(OPTION1_ON, OPTION2_OFF);

гораздо более читабельным, чем

doSomething(true, false);

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


0

Я бы сказал, что сплоченность может быть проблемой.

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

Если вы решите сделать все по частям, чтобы оставаться СУХИМЫМ и гладким, вам придется добавить множество условий, касающихся того, просматривает ли пользователь права администратора или нет, и загромождать код ненужными вещами, что сделает его крайне нечитаемым, скажем так дизайнер!

Таким образом, в такой ситуации, даже если код в значительной степени такой же, просто потому, что он может масштабироваться до чего-то другого, а варианты использования могут немного измениться, было бы плохо следовать каждому из них, добавляя условия и ifs. Таким образом, хорошей стратегией было бы отказаться от концепции СУХОЙ и разбить код на обслуживаемые части.


0
  • Когда меньшее количество кода не выполняет ту же работу, что и большее количество кода. Рефакторинг для простоты - это хорошо, но вы должны позаботиться о том, чтобы не упрощать проблемное пространство, с которым сталкивается это решение. 980 строк кода могут обрабатывать больше угловых случаев, чем 450.
  • Когда меньше кода не сработает так же изящно, как больше кода. Я видел несколько «ref *** toring» заданий, выполненных в коде для удаления «ненужных» try-catch и других обработок ошибок. Неизбежным результатом было вместо того, чтобы показать диалоговое окно с хорошим сообщением об ошибке и о том, что пользователь мог сделать, приложение упало или YSODed.
  • Когда меньше кода менее обслуживаемо / расширяемо, чем больше кода. Рефакторинг для краткости кода часто удаляет «ненужные» конструкции кода в интересах LoC. Проблема в том, что эти конструкции кода, такие как объявления параллельного интерфейса, извлеченные методы / подклассы и т. Д., Необходимы, если этот код должен когда-либо делать больше, чем в настоящее время, или делать это иначе. В крайнем случае, некоторые решения, специально разработанные для конкретной проблемы, могут вообще не работать, если определение проблемы немного меняется.

    Один пример; у вас есть список целых чисел. Каждое из этих целых чисел имеет дублирующее значение в списке, кроме одного. Ваш алгоритм должен найти это непарное значение. В общем случае решение состоит в том, чтобы сравнивать каждое число с любым другим числом, пока не найдете число, которое не имеет дублирования в списке, что является N ^ 2-кратной операцией. Вы также можете построить гистограмму, используя хеш-таблицу, но это очень мало места. Тем не менее, вы можете сделать это линейным и постоянным пространством, используя побитовую операцию XOR; XOR каждое целое число против бегущей «суммы» (начиная с нуля), и, в конце, бегущая сумма будет значением вашего непарного целого числа. Очень элегантный. Пока требования не изменятся, и более одного числа в списке могут быть непарными, или целые числа включают ноль. Теперь ваша программа либо возвращает мусор, либо неоднозначные результаты (если она возвращает ноль, означает ли это, что все элементы являются парными, или что непарный элемент равен нулю?). Такова проблема «умных» реализаций в реальном программировании.

  • Когда меньше кода меньше самодокументируемого, чем больше кода. Возможность читать сам код и определять, что он делает, очень важна для командной разработки. Предоставление написанного вами алгоритма brain-f ***, который прекрасно работает для начинающего разработчика, и попросить его настроить его, чтобы немного изменить вывод, не слишком далеко продвинет вас. Многие старшие разработчики будут иметь проблемы с этой ситуацией. Возможность в любой момент понять, что делает код и что с ним может пойти не так, является ключом к рабочей среде разработки команды (и даже в одиночку; я гарантирую вам, что вспышка гения у вас была, когда вы писали 5 метод лечения рака уйдет далеко в прошлое, когда вы вернетесь к этой функции, пытаясь излечить болезнь Паркинсона.)

0

Компьютерный код должен сделать несколько вещей. «Минималистский» код, который этого не делает, не является хорошим кодом.

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

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

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


Долг в глазах смотрящего.

-2

Вычислительная производительность. При оптимизации параллельной или конвейерной части вашего кода может быть полезно, например, не цикл от 1 до 400, а от 1 до 50 и поместить 8 экземпляров аналогичного кода в каждый цикл. Я не предполагаю, что это имело место в вашей ситуации, но это пример, где больше строк лучше (с точки зрения производительности).


4
Хороший компилятор должен лучше, чем обычный программист, знать, как развернуть циклы для конкретной архитектуры компьютера, но общая идея верна. Однажды я посмотрел исходный код подпрограммы умножения матриц из высокопроизводительной библиотеки Cray. Умножение матриц - это три вложенных цикла и всего около 6 строк кода, верно? Неправильно - эта библиотечная подпрограмма содержала около 1100 строк кода, плюс такое же количество строк комментариев, объясняющих, почему она была такой длинной!
Алефзеро

1
@alephzero Ух, я бы хотел увидеть этот код, это должен быть просто Cray Cray.

@alephzero, хорошие компиляторы могут многое, но, к сожалению, не все. Хорошая сторона в том, что это то, что делает программирование интересным!
Ганс Янссен

2
@alephzero Действительно, хороший код умножения матриц не просто сбрасывает немного времени (т. е. сокращает его на постоянный множитель), он использует совершенно другой алгоритм с другой асимптотической сложностью, например алгоритм Штрассена примерно равен O (n ^ 2.8) а не O (n ^ 3).
Артур Такка
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.