Роберт Мартин нехило так повлиял на айти‑индустрию. Он придумал принципы SOLID, о которых спрашивают на собесах, пишут статьи на Хабре и спорят в комментариях. Он написал книгу «Чистый код» и сделал это словосочетание айтишным мемом. Если зайти на хэдхантер, вбить в поиске слове «чистый», выбрать специализацию «Программист, разработчик» и нажать «Найти», получим больше семисот вакансий. Про чистоту кода и архитектуры спорят на код‑ревью, в комментариях и статьях по всему интернету. Разговоров о чистоте внутри айти‑тусовки бывает так много, словно мы находимся в сообществе клинеров, а не программистов.
Мартин называет себя «дядюшкой Бобом». В своих работах он выступает в образе опытного мудрого и взрослого родственника, который несёт свет и знания таким зелёным и неопытным племянникам. И у него отлично получилось втереться в доверие! Типичный хороший программист‑анальник бессилен перед таким добрым дядей. И я знаю, о чём пишу. Восемь лет назад я сам запоем читал книги дядюшки, а потом до усрачки защищал чистоту кода на код‑ревью. Я на себе почувствовал, насколько Роберт Мартин отличный агитатор и пропагандист. Работая с другими людьми, читая статьи и обсуждения на Хабре и хакерньюс, анализирую требования к вакансиям, я понимаю, что не я один попался на отличную пропаганду от «дядюшки Боба».
Книга «Чистый код» вышла 2008 году. Кажется, что за это время десятки тысяч программистов должны были обжечься на советах Роберта Мартина, испоганить ни одну сотню кодовых баз и сделать выводы. Но ничего подобного! «Чистый код» продолжают советовать новичкам, спрашивать о нём на собесах, использовать агитки Мартина в кодовых базах и как аргументы на код‑ревью. Пора с этим кончать. Пора развенчать культ личности «дядюшки Боба». Ведь никакой он не дядюшка. И программист тоже никакой. Сейчас я это докажу. Погнали вместе читать листинги из книги.
У меня российское издание 2016 года. Ниже я переписал листинг 10.6. В книге код на джаве, но я не джавист, поэтому повторил код один‑в-один на тайпскрипте. Это пример чистого кода от Роберта Мартина. С точки зрения Мартина, в этом листинге содержательные имена переменных, а имена функций отлично комментируют код. Шестьдесят строчек чистого кайфа и наслаждения.
class PrimeGenerator {
private static primes: number[];
private static multiplesOfPrimeFactors: number[];
static generate(n: number): number[] {
this.primes = new Array(n);
this.multiplesOfPrimeFactors = [];
this.set2AsFirstPrime();
this.checkOddNumbersForSubsequentPrimes();
return this.primes;
}
private static set2AsFirstPrime() {
this.primes[0] = 2;
this.multiplesOfPrimeFactors.push(2);
}
private static checkOddNumbersForSubsequentPrimes() {
let primeIndex = 1;
for (let candidate = 3; primeIndex < this.primes.length; candidate += 2) {
if (this.isPrime(candidate))
this.primes[primeIndex++] = candidate;
}
}
private static isPrime(candidate: number): boolean {
if (this.isLeastRelevantMultipleOfNextLargerPrimeFactor(candidate)) {
this.multiplesOfPrimeFactors.push(candidate);
return false;
}
return this.isNotMultipleOfAnyPreviousPrimeFactor(candidate);
}
private static isLeastRelevantMultipleOfNextLargerPrimeFactor(candidate: number) {
let nextLargerPrimeFactor = this.primes[this.multiplesOfPrimeFactors.length];
let leastRelevantMultiple = nextLargerPrimeFactor * nextLargerPrimeFactor;
return candidate === leastRelevantMultiple;
}
private static isNotMultipleOfAnyPreviousPrimeFactor(candidate: number) {
for (let n = 1; n < this.multiplesOfPrimeFactors.length; n++) {
if (this.isMultipleOfNthPrimeFactor(candidate, n))
return false;
}
return true;
}
private static isMultipleOfNthPrimeFactor(candidate: number, n: number) {
return candidate == this.smallestOddNthMultipleNotLessThanCandidate(candidate, n);
}
private static smallestOddNthMultipleNotLessThanCandidate(candidate: number, n: number) {
let multiple = this.multiplesOfPrimeFactors[n];
while (multiple < candidate) {
multiple += 2 * this.primes[n];
}
this.multiplesOfPrimeFactors[n] = multiple;
return multiple;
}
}
Этот код генерирует список из N простых чисел. Понять алгоритм, по которому он это делает, очень непросто. Если бы я попросил кандидата решить подобную задачу и в ответ получил бы такие шестьдесят строк, то кандидат не прошёл бы собеседование.
Мартин советует давать методам названия длиной в 46 символов. Пока читаешь такое название, можно забыть, что вообще происходит. Вот эта колбаса isLeastRelevantMultipleOfNextLargerPrimeFactor
похоже скорее на какой-то прикол, а не на попытки сделать код понятным для других членов команды.
Но даже если сократить эти колбасы до бэйби-сарделек, лучше не станет. Эти названия нехило так вводят в заблуждение. Не знаю, кем нужно быть, чтобы ожидать мутации данных в методе с названием isPrime
. Принимаем number на вход, на выходе даём boolean, а ещё заодно данные мутируем, но ты не переживай, всё хорошо. Эта мутация размазана ровным слоем по семи приватным методам, чтобы ты точно не решил разбираться в происходящем. Программист, которому в эту лапшу придётся вносить изменения, охренеет в голове выстраивать дата-флоу, сделает git blame, распечатает листинг на ватмане, вычислит автора по айпи и засунет этот листинг автору в анальное отверстие. Мне сложно представить другое развитие событий.
Нормальный человек решит эту задачу внутри одной функции на 19 строчек кода. Тут не нужны кучи вспомогательных методов с нечитаемыми названиями и скрытыми мутациями. Всего одна функция или статический метод.
function generatePrimes(count: number) {
const primes: number[] = [];
let primeCandidate = 2;
while (primes.length !== count) {
let candidateIsPrime = true;
for (let i = 2; i <= Math.sqrt(primeCandidate); i++) {
if (primeCandidate % i === 0) {
candidateIsPrime = false;
break;
}
}
if (candidateIsPrime) primes.push(primeCandidate);
primeCandidate += 1;
}
return primes;
}
Этот код проще, понятнее и его легче поменять. Хотя бы потому что в нём в три раза меньше строк. Чистый ли это код? Если сравнивать с кодом Мартина, то точно не чистый. Но мне плевать! В этом коде явный дата-флоу, явные мутации, он короткий и атомарный. И это главное, этого достаточно.
Давайте глянем ещё на один короткий листинг и на этом закончим. Вот пример теста прямо на джаве. Переписывать его не стал.
@Test
public void turnOnLoTempAlarmAtThreashold() throws Exception {
hw.setTemp(WAY_TOO_COLD);
controller.tic();
assertTrue(hw.heaterState());
assertTrue(hw.blowerState());
assertFalse(hw.coolerState());
assertFalse(hw.hiTempAlarm());
assertTrue(hw.loTempAlarm());
}
Нормальный тест, на мой вкус. Как и в прошлом листинге, у меня здесь вопросы к неймингу. Я бы назвал методы типа isHeaterEnabled
, но это вкусовщина. В целом, тест довольно наглядный. Но Роберту этот тест не понравился, и он отрефакторил его вот так:
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
wayTooCold();
assertEquals(“HBchL”, hw.getState());
}
Он называет функцию wayTooCold
более понятной. Но название этой функции ничего не говорит! Что в ней происходит, что она абстрагирует? Функция должна что-то делать, поэтому в названии она, как правило, содержит глагол. Но тут нет никакого глагола, здесь только прилагательное. Не знаю, каким гением нужно быть, чтобы, читая этот код, не проваливаться в объявление wayTooCold
. Ненужного абстрагирования Роберту мало, поэтому он решил изобрести свой доменный язык для отображения состояния системы. Большая буква -- значит true, маленькая -- false. Говорит, что в такой записи сложнее допустить ошибку, чем в прямых как палка assertTrue
и assertFalse
. Подтверждений у Мартина никаких нет. Всё учение о чистом коде строится лишь на вере.
Вообще, Роберт Мартин в своей книге делает очень много заявлений и лозунгов, иллюстрирует их отвратительными примерами кода и не приводит никаких подтверждений. Этим книга "Чистый код" кардинально отличается от "Совершенного кода" Макконнелла. В "Совершенном коде" нет лозунгов, зато есть ссылки на научные работы и комплексный взгляд на разработку ПО. Да, книга Макконнелла старенькая, но если хочется что-то почитать про написание понятного и поддерживаемого кода, то лучше взять в руки именно её, а не пропагандистскую агитку от "дядюшки Боба", которая точно испортит вашу кодовую базу.
Я привел всего два листинга. Но если этих двух примеров недостаточно, чтобы перестать верить в чистый код и пророка его "дядюшку Боба", то значит вера ваша слишком крепка, и я перед ней бессилен. Ничего не поделать. Возможно, наступит момент, когда вы тоже возьмёте в руки свою печатную копию учения о чистом коде, откроете листинги, внимательно их почитаете и ужаснётесь. В этот момент вам откроется истина. Просто сейчас вы к ней не готовы.
Комментарии (23)
NeoNN
21.01.2025 16:10Есть одна интересная книга, называется A philosophy of software design, автор John Ousterhout, и она рассказывает о когнитивной сложности в разработке ПО и методах ее упрощения. И некоторые ее положения весьма далеки от догм энтерпрайзного классоклепания, от чего у кучи народа подгорает и бомбит. Как же так - не клепать классы на каждый чих, или писать комменты на высокоуровневую структуру и неявные связи компонент - ведь мой код написан по солид, а значит непогрешим! Бабах. Но если книженцию почитать и вдуматься, то это тот подход к разработке ПО, который мы потеряли где-то во времена становления "агильности".
Telichkin Автор
21.01.2025 16:10Спасибо за рекомендацию! Уже не первый раз встречаю положительный отзыв на эту книгу. Надо бы почитать
ZhetoN
21.01.2025 16:10заголовок кликбейтный, правильный заголовок звучит так "В 2024 году я наконец-то добрался до книги, купленной мною в 2016, прочитав я осознал что она идет в разрез с моим видением мира, и поскольку выплеснуть свое несогласие и негатив больше было некуда, решил тут"
powerman
21.01.2025 16:10Вы упускаете из виду контекст. Лично я никогда не писал на Java и примеры кода в его книжках меня тоже не восхищают. Но я вполне допускаю, что конкретно для Java того времени - этот код может быть заметно лучше среднего. То же касается и SOLID - будучи в целом здравым подходом оно не везде и не всегда полезно на практике (напр. в не ООП языках и "не совсем ООП" языках вроде Go где нет наследования от LSP пользы нет, равно как в небольших компаниях/проектах где один источник требований нет пользы от SRP, DIP стала настолько штатным и привычным механизмом что сегодня нет смысла её как-то особенно выделять, подход к интерфейсам в том же Go делает бессмысленным явное соблюдение ISP - оно само собой соблюдается, …). Но это не отменяет полезности всех частей SOLID в те времена и для тех языков и проектов, на которых SOLID был сформулирован. И тем более не отменяет полезности и адекватности идей, которые продвигает дядя Боб - просто надо понимать суть этих идей и уметь адаптировать их для своего текущего проекта, а не тупо заучивать формальные определения каждой буквы из SOLID и столь же тупо требовать использования их всех в любых проектах.
SamDark
21.01.2025 16:10Книги неплохие. Они заставляют подумать. Проблема не в книгах, а в фанатиках, которые бездумно воспринимают информацию и не проверяя верят ей.
Telichkin Автор
21.01.2025 16:10По ощущениям мне кажется (ссылок на пабмед не будет), что книги Мартина сформировали наибольшее количество фанатиков. Они написаны в таком безапелляционном стиле и на словах пропагандируют за всё хорошее и против всего плохого. На мой вкус, виноваты не только фанатики.
Хотя, возможно, это я просто так рационализирую то, что когда-то сам был фанатиком...
SamDark
21.01.2025 16:10Ну это просто подача такая. Есть ещё, например, Бугаенко. Он в том же стиле пишет и тоже нельзя верить на слово (как впрочем и тем, кто пишет в менее уверенном стиле).
mynameco
21.01.2025 16:10мне кажется читать книги нужно не в поисках истины а в поисках себя.
я когда начинал, читал книги, и много не понимал, потом перечитывал, сверялся и понимал, а потом перечитывал, и был не согласен.
mynameco
21.01.2025 16:10Поясню. есть строительство домов. например малоэтажное. выбор для тех стека, огромен. И все будет работать. а есть пятиэтажки. пятиэтажки строят другие люди, у них другие требования. другие расчеты. тот кто строит пятиэтажку, построит легко двухэтажный дом. тот кто строит двухэтажные дома, не построит пятиэтажку. далее. 16 этажки. там начинаются такие требования, что пятиэтажники не сталкиваются. подъем воды. пожарная безопасность. кондиционирование и всякое такое. 16 этажники, построят 5 этажку, пятиэтажники, не построят 16 этажку. А те кто строят аля бурдж халиф, у них вообще такие патерны, что кажутся оверинженерингом. и 16 этажники не построят такое. т.е. все правы, но есть ньюанс. каждую задачу нужно решать на своем уровне. для кого то патерны это мелочь, для кого то - основа. Вспоминается история, когда мне один хорший программист, задвигал что мультивертуальные методы в языке, это важно, потому что решать задачу, перкидывания одной шмотки на другую в инвентарях, только так и решить. но оказывается, в некоторых играх, перекидывание шмоток, это настолько высокоуровнеевый код, что само понятие языка программирования, там не имеет значение. и это далеко также как, например, обновление хп персонажа проперти в коде. и как биндинг поля в модели из распределенной базы данных.
Jijiki
21.01.2025 16:10хорошо, есть частица - точка, есть грани куба - а виртуально или куб или сфера, она летит как-то, как-то мы узнали о каком-то методе и вот мы уже начинаем читать тонну книг по этой тематике, как тут найти себя, если вопрос интересный стоит надо чтоб симуляция не лагала или была приемлемой на единицу времени, а отсюда связь со структурами, если дом это стек - то документации на стек не достаточно, наверняка есть какието книги или наблюдения/хитрости/тонкости
Vitaly_js
21.01.2025 16:10Посмотрел оригинал и все не так страшно как может показаться после прочтения данной статьи.
Можно немножко навалить контекста. Изначально это был длиннющий код Кнута, который автор и решил "оптимизировать". Причем идея была не менять алгоритм оригинала.
Данный пример я бы не сказал, что корректно перенесен на ts. Сам я Джаву не знаю, но уже беглого знакомства с исходниками и последующих комментариев Мартина понятно для чего использовался класс. На ts что бы получить все теже самые ништяки и даже выглядейть будет почти как на джаве достаточно использовать замыкания и получится что-то типа:
```
function primeGenerator(n: number) {
const primes = Array<number>(n)
const multiplesOfPrimeFactors: number[] = []
set2AsFirstPrime();
checkOddNumbersForSubsequentPrimes();
return primes;
function set2AsFirstPrime() {
primes[0] = 2;
multiplesOfPrimeFactors.push(2);
}
function checkOddNumbersForSubsequentPrimes() {
let primeIndex = 1;
for (let candidate = 3; primeIndex < this.primes.length; candidate += 2) {
if (this.isPrime(candidate))
this.primes[primeIndex++] = candidate;
}
}
...
}
```
А вот оригинал
```
public class PrimeGenerator {
private static int[] primes;
private static ArrayList<Integer> multiplesOfPrimeFactors;
protected static int[] generate(int n) {
primes = new int[n];
multiplesOfPrimeFactors = new ArrayList<Integer>();
set2AsFirstPrime();
checkOddNumbersForSubsequentPrimes();
return primes;
}
private static void set2AsFirstPrime() {
primes[0] = 2;
multiplesOfPrimeFactors.add(2);
}
...
}
```
Ну, т.е. статичный класс и методы делают все то, что на js доступно из коробки с помощью замыканий. А так как сама задача это переписывание алгоритма сверху вниз все функции легко выстраиваются в порядке необходиом для их вызова.
По поводу длиннющих названий. Мне такое читать было дискомфортно. Я английский читаю именно выражениями и когда нет пробелов к которым я привык, мне стало неудобно. Но я вполне допускаю, что носитель языка легко справится с такими именами.
А лаконичные имена в коде написанном русскоговорящим разрабом являются действительно более подходящими для чистого кода.
А вот пристрастие пилить на функции в том смысле в котором это сделано сдесь - это дискуссионный вопрос. Даже в данном примере, на мой взгляд, достаточно пробелов до и после кода внутри set2AsFirstPrime вместо вынесения этого в отдельную функцию, комментариев не нужно. Да и в целом заменить `// you comment` на `youComment()` довольно сомнительное решение. Если убрать часть таких одноразовых функций и предварить код комментарием будет, как минимум, не хуже. А просто потому, что одну простыню заменяем на другую. Но одна читается за проход сверху вниз, а другая заставляет скакать глазами в произвольном порядке.
brutfooorcer
21.01.2025 16:10Если я верно помню, важным посылом было стараться писать код так, что бы его можно было "читать, как книгу". Отсюда и такие длинные названия. Думаю, носителю языка действительно может и не будет дискомфортно читать это.
GospodinKolhoznik
21.01.2025 16:10Что же вы критикуете (не)дядюшку Боба по его книге 2008 года? Не логичнее ли сегодня рассматривать его книгу 2024 года Functional Design: Principles, Patterns, and Practices, в которой он преподносит свои принципы SOLID уже в иной форме, чем раньше. (Да, теперь он топит за функциональщину!) Хотя он сам признаётся, что его новая книга по сути представляет из себя вольное переизложение старой доброй SICP.
Telichkin Автор
21.01.2025 16:10Потому что книгу 2008 до сих пор рекомендуют к прочтению, спрашивают про чистый код на собесах и срутся по поводу чистоты на код-реаью. Когда новая книга завирусится так же сильно, можно будет и её прочитать и покритиковать.
powerman
21.01.2025 16:10Вы путаете причины со следствиями. Причина происходящего в неумении проводить собесы и делать ревью, а не в дяде Бобе. Если запретить на собесах и ревью упоминать SOLID и чистый код - их место просто займёт какой-то другой карго-культ.
IUIUIUIUIUIUIUI
21.01.2025 16:10Не логичнее ли сегодня рассматривать его книгу 2024 года Functional Design: Principles, Patterns, and Practices, в которой он преподносит свои принципы SOLID уже в иной форме, чем раньше.
Так она ещё хуже, потому что:
-
Обсуждаемая книга 2008-го года о продакшен-коде написана человеком, который не писал продакшен-код 18 лет, а книга 2024-го года о продакшен-коде написана человеком, который не писал продакшен-код 34 года, и это видно.
Недядя Боб не понимает функциональщину ещё больше, чем он не понимает ООП.
Если цель — найти причины не любить Боба, то это отличная книга. Если цель — научиться разрабатывать ПО в функциональной парадигме, то это отвратительная книга, и книги domain modeling made functional или algebra-driven design лучше в <целочисленное переполнение> число раз.
-
WASD1
21.01.2025 16:10При проверке на простоту достаточно проверить не делимость только на простые числа.
Учитывая, что список простых чисел у вас уже есть, причём в отсортированном виде - можно и нужно идти по нему.
DasMeister
21.01.2025 16:10Типичный Эскобар. Выберешь алгоритмический примитивный пример и попытаешься показать принцип формирования и сегрегации функционала, критик скажет - зачем всё это, если кода будет больше.
Если возьмешь пример с бизнес кодом, то пока объяснишь доменную область, напишешь текста больше чем во всей книге. И обвинят в генерации воды.
Что же делать несчастному старику, который пытался донести идеи, а породил два лагеря религиозных фанатиков?
Jijiki
21.01.2025 16:10а я открыл формулы и гдето итерации с 10 по коду я остановился но тут - решение в целом не правильное ) не хватает точности какогото фактора )
WASD1
21.01.2025 16:10решение в целом же неверное (ну вы же даблы не всерьёз используете, да?).
primes_count(x) = log(x) - это формула примерная и на неё полагаться нельзя.
Правильным будет то, что ниже.
*) Ну и примитивное "решето Эратосфена" же всё равно быстрее, если можете позволить себе память.def calc_primes( primes_cnt): primes = [] candidate = 2 while ( len( primes) < primes_cnt): candidate_is_prime = True sqrt_check = (int)(candidate ** (1/2)) for i in primes: # +1 - на всякий случай не доверяем округлению флотов if i > sqrt_check + 1: break if candidate % i == 0: candidate_is_prime = False break if candidate_is_prime: primes.append( candidate) candidate += 1 return primes
bugy
Согласен насчёт совершенного кода. Я сперва прочитал её и на многое открылись глаза
Потом мельком видел чистый код, и не понял, почему люди фанатеют от весьма спорных утверждений в этой книге.