Разработчик с мозгом Груга (перевод)
Оригинал: https://grugbrain.dev/ (c) Carson Gross
Простое руководство для разработчика с ясным (и не замутнённым лишним) мышлением
Введение
Этот сборник мыслей о разработке программного обеспечения собран разработчиком с мышлением Груга.
Я не самый умный разработчик, но я программирую уже много лет и кое-чему научился, хотя во многом до сих пор не разбираюсь.
Я попытался собрать свои уроки на одной небольшой, легко усваиваемой и забавной странице. Это не только для вас, юные разработчики, но и для меня самого, потому что с возрастом я начал забывать важные вещи, например, что ел на завтрак или надел ли штаны.
Есть много разработчиков с большим мозгом (умников), и некоторым из них это, скорее всего, не понравится, они скорчат кислую мину.
А тех, кто ДУМАЕТ, что они умники, ещё больше, и им это уж точно, определённо, возможно, не понравится, и кислых мин будет много (таков интернет).
(Примечание: когда-то я и сам думал, что я из таких, но жизнь научила меня уму-разуму на горьком опыте.)
Ну и ладно!
Вроде как у нас свободная страна, и в конечном счёте это не так уж и важно. Но я надеюсь, вы получите удовольствие от чтения и, возможно, чему-то научитесь на моих многочисленных ошибках, совершённых за долгую программистскую жизнь.
Вечный враг: Сложность
Главный враг разработчика — это сложность.
Сложность — это плохо.
Повторю ещё раз:
Сложность — это очень плохо.
А теперь скажите вы:
Сложность — это очень, очень плохо.
Если бы мне пришлось выбирать между сложностью и схваткой один на один с тираннозавром, я бы выбрал тираннозавра: его по крайней мере видно.
Сложность — это злой дух, который проникает в кодовую базу через доброжелательных, но в конечном счёте заслуживающих удара дубинкой разработчиков (не из клана Груга) и менеджеров проектов, которые не боятся этого демона сложности, а иногда даже не знают о его существовании.
В один прекрасный день кодовая база понятна, я могу спокойно делать свою работу, и всё хорошо!
На следующий день это уже невозможно: демон сложности проник в код, и ситуация стала очень опасной!
Я не вижу демона сложности, но чувствую его присутствие в кодовой базе.
Этот демон сложности издевается надо мной: вносишь изменение здесь, а ломается что-то совершенно не связанное там. Что?! Издевается, насмехается, ха-ха, как смешно. А я ведь так люблю программирование и рад, что не стал биржевым спекулянтом, как советовал мой наставник.
Дубинка не действует на демона сложности, а бить дубинкой разработчика, который впустил этого духа — плохая идея. Иногда этим разработчиком оказываюсь я сам!
К сожалению, часто это именно я.
Поэтому я говорю и буду говорить часто: сложность — это очень, очень плохо.
Умение говорить «нет»
Лучшее оружие против демона сложности — это волшебное слово «нет».
«Нет, я не буду делать эту фичу».
«Нет, я не буду создавать эту абстракцию».
«Нет, я не буду каждый день обливаться водой и пить меньше чёрного „думательного“ сока, прекрати спрашивать».
Заметьте, это хороший инженерный совет, но плохой карьерный совет. «Да» — это волшебное слово для увеличения кучи блестящих камушков и получения власти над большим племенем разработчиков.
Печально, но факт: научитесь говорить «да», а затем научитесь обвинять других, когда всё пойдёт не так — вот идеальный карьерный совет.
Но я должен быть честен с самим собой, и «нет» — моё волшебное слово. Поначалу его трудно произносить, особенно если вы по натуре добрый человек и не любите разочаровывать людей (а таких много!), но со временем становится легче, даже если ваша гора блестящих камушков будет не такой высокой, как могла бы.
И это нормально: в конце концов, сколько этих блестящих камушков мне на самом деле нужно?
Умение говорить «окей»
Иногда компромисс необходим, иначе не будет блестящих камушков, а значит, и мяса динозавра. Это плохо. Жена строго напоминает мне о детях, которым нужна крыша над головой, еда и так далее, и её совершенно не интересуют мои тирады о демоне сложности, которые она слышит уже в пятидесятый раз.
В такой ситуации я рекомендую говорить «окей».
«Окей, я сделаю эту фичу».
А затем я трачу время на то, чтобы придумать решение 80/20 для этой проблемы, и реализую именно его.
Решение 80/20 означает «получить 80% желаемого, написав 20% кода». Возможно, в таком решении не будет всех тех «свистелок и перделок», которые хотел менеджер проекта, оно может быть немного корявым, но оно работает, приносит основную ценность и по большей части держит демона сложности на расстоянии.
Иногда, наверное, лучше просто не говорить менеджеру проекта и сделать всё по принципу 80/20. Прощение получить легче, чем разрешение. Менеджеры проектов бывают перегружены, их мысли порхают, как бабочки, и они имеют дело со множеством разработчиков. Часто они забывают, что вообще должна была делать фича, или переключаются на другое, или уходят, или их увольняют — я видел много таких случаев.
В любом случае, такой подход обычно в интересах самого менеджера проекта, так что мне не слишком совестно.
Факторизация кода
Следующая стратегия гораздо сложнее: правильное разделение кодовой базы (умное слово: «правильно факторизуйте свой код»). Здесь трудно дать общий совет, потому что каждая система уникальна. Однако я пришёл к одному убеждению: не факторизуйте приложение слишком рано!
В начале проекта всё очень абстрактно и похоже на воду: моему борющемуся мозгу почти не за что уцепиться. Нужно время, чтобы у системы появилась «форма», и чтобы я понял, что она вообще делает. Я стараюсь не заниматься факторизацией на ранних этапах проекта, и тогда, в какой-то момент, из кодовой базы сами собой вырисовываются удачные точки разделения.
Хорошая точка разделения имеет узкий интерфейс с остальной системой: небольшое количество функций или абстракций, которые скрывают демона сложности внутри, словно он заточён в кристалле.
Я испытываю огромное удовлетворение, когда демон сложности надёжно заточён в кристалле. Это лучшее чувство — поймать в ловушку своего смертельного врага!
Я стараюсь терпеливо наблюдать, как появляются точки разделения, и постепенно рефакторить код, позволяя кодовой базе обретать форму со временем и с накоплением опыта. Для этого нет жёстких правил: я узнаю точку разделения, когда её вижу. Просто нужно время, чтобы развить этот навык, и терпение.
Иногда я тороплюсь и создаю неправильные абстракции, поэтому я склонен скорее подождать.
Разработчикам с большим мозгом это часто совсем не нравится, и они изобретают множество абстракций в самом начале проекта.
Меня так и подмывает схватиться за дубинку и закричать: «Умники не поддерживают код! Они переходят в следующий архитектурный комитет, оставляя код на растерзание таким, как я!»
Но я научился контролировать свои порывы — это главное отличие человека от животного.
Вместо этого я стараюсь ограничить ущерб от «умников» в начале проекта, давая им задания вроде создания UML-диаграммы (коду не навредит, всё равно её, скорее всего, выбросят) или требуя работающее демо к завтрашнему дню.
Работающее демо — особенно хороший трюк: он заставляет умника сделать что-то, что действительно работает, чтобы было что обсуждать и на что посмотреть. Это поможет ему быстрее увидеть реальное положение дел.
Помните! У умников действительно большой мозг! Его нужно лишь направить на благие дела, а не на случайное служение демону сложности, что случается сплошь и рядом.
(Лучшие из нашего племени способны направить нескольких умников в правильном направлении и создать множество кристаллов-ловушек для демона сложности. Таких ждёт огромная гора блестящих камушков!)
Кстати, демо-подход иногда называют «прототипированием» — для менеджера проекта это звучит солиднее.
Я говорю: создавайте прототипы на ранних стадиях разработки, особенно если в команде много умников.
Тестирование
У меня с тестами отношения любви и ненависти: тесты спасали меня бесчисленное количество раз, и я люблю и уважаю их.
К сожалению, существует также много «шаманов тестирования». Некоторые из них делают из тестов идола, требуя таких вещей, как «сначала тест», ещё до того, как я написал код или вообще понял, что я делаю в этой предметной области!
Как я могу тестировать то, в чём я ещё даже не разобрался?!
«О, не волнуйся: тесты покажут тебе, что нужно делать».
Я снова ловлю себя на том, что медленно тянусь к дубинке, но сохраняю спокойствие.
Вместо этого я предпочитаю писать большинство тестов после этапа прототипирования, когда код начинает обретать устойчивую форму.
Но, заметьте: здесь я должен быть очень дисциплинирован!
Легко перейти к следующей задаче и не написать тесты, потому что «у меня на машине всё работает»!
Это очень, очень плохо: нет гарантии, что это будет работать на другой машине, и нет гарантии, что это будет работать на моей машине в будущем, что случалось много раз.
Шаманы тестирования правы в том, что тесты важны, даже если сами они часто за всю жизнь не сделали ни одной полезной фичи и только и говорят, что о тестах. Они заслуживают дубинки, но намерения у них благие.
Кроме того, шаманы тестирования часто говорят о модульных тестах, но я не нахожу их такими уж полезными. Мой опыт показывает, что идеальные тесты — это не модульные и не сквозные тесты, а что-то среднее.
Модульные тесты (unit tests) — это нормально, но они ломаются при изменении реализации (которая меняется гораздо чаще, чем API!), затрудняют рефакторинг и, честно говоря, всё равно пропускают много багов, которые часто возникают из-за взаимодействия с другим кодом. Их часто выбрасывают при изменении кода.
Я пишу модульные тесты в основном в начале проекта, чтобы помочь делу сдвинуться с мёртвой точки, но не слишком привязываюсь к ним и не ожидаю долгосрочной пользы.
Сквозные тесты (end-to-end) хороши, они показывают, что вся система работает, но! Когда они ломаются, трудно понять, в чём дело, и это часто сводит меня с ума. Иногда мы просто начинаем их игнорировать, потому что «ой, да они постоянно ломаются». Это очень плохо!
Промежуточные тесты, которые шаманы иногда с кислой миной называют «интеграционными тестами», по-моему, являются золотой серединой. Они достаточно высокого уровня, чтобы проверять корректность системы, и достаточно низкого, чтобы с хорошим отладчиком было легко понять, что сломалось.
Я предпочитаю иметь некоторое количество модульных тестов, особенно в начале, но не покрывать ими 100% кода и уж точно не писать «сначала тест». Подход «тестировать по ходу дела» для меня работает довольно хорошо, особенно когда я разбираюсь в задаче.
Я направляю свои самые яростные усилия на интеграционные тесты, когда появляются точки разделения и система стабилизируется! API в этих точках, надеюсь, будет стабильным по сравнению с реализацией, и интеграционные тесты будут оставаться ценными долгое время, а также их будет легко отлаживать.
Также создаётся небольшой, тщательно подобранный набор сквозных тестов, который поддерживается в рабочем состоянии под страхом удара дубинкой. Основное внимание в этих тестах уделяется самым важным пользовательским сценариям и нескольким ключевым пограничным случаям, но их не должно быть слишком много, иначе их станет невозможно поддерживать, и их начнут игнорировать.
Для меня это идеальный набор тестов.
Вам это может не понравиться, но это вершина моего мастерства в тестировании.
Кроме того, я не люблю моки (mocking) в тестах, предпочитаю использовать их только в случае крайней необходимости (редко/никогда) и только для крупномодульной имитации (на уровне точек разделения/систем).
Одно исключение из моей нелюбви к подходу «сначала тест»: когда найден баг. Я всегда стараюсь сначала воспроизвести баг с помощью регрессионного теста, а затем исправить его. Только в этом случае почему-то такой подход работает лучше.
Agile
Я думаю, что Agile — это не ужасно, но и не хорошо.
В конечном счёте, это не худший способ организации разработки, может быть, даже лучше других. Полагаю, это нормально.
Однако опасность представляют agile-шаманы! Из-за них было потеряно очень-очень много блестящих камушков!
Всякий раз, когда agile-проект проваливается, agile-шаман говорит: «Вы просто неправильно делали Agile!» Я замечаю, что это ужасно удобно для agile-шамана, который просит ещё блестящих камушков, чтобы лучше обучить молодых разработчиков Agile. Опасно!
Меня подмывает схватиться за дубинку, когда разговоров об Agile становится слишком много, но я всегда сохраняю спокойствие.
Прототипирование, инструменты и найм хороших разработчиков — вот ключ к успеху в разработке ПО. Agile-процесс — это нормально и иногда помогает, но если относиться к нему слишком серьёзно, он может и навредить.
Я говорю, что нет серебряной дубинки, которая решит все проблемы в разработке, что бы там ни говорил agile-шаман (опасно!).
Рефакторинг
Рефакторинг — это прекрасное и часто полезное занятие, особенно на поздних стадиях проекта, когда код уже устоялся.
Однако я заметил, что много раз за свою карьеру «рефакторинги» шли совершенно наперекосяк и в итоге приносили больше вреда, чем пользы.
Я не уверен точно, почему одни рефакторинги проходят успешно, а другие проваливаются, но я заметил, что чем масштабнее рефакторинг, тем выше вероятность неудачи.
Поэтому я стараюсь, чтобы рефакторинги были относительно небольшими и чтобы во время них «не заплывать слишком далеко от берега». В идеале система должна работать всё время, и каждый шаг должен быть завершён до начала следующего.
Сквозные тесты здесь — настоящее спасение, но часто очень трудно понять, почему они сломались… такова жизнь при рефакторинге.
Также я заметил, что введение слишком большого количества абстракций часто приводит к провалу рефакторинга и всей системы. Хороший пример — введение J2EE: множество умников сидели и придумывали слишком много абстракций, и ничего хорошего из этого не вышло, многие проекты пострадали.
Другой хороший пример: в компании, где я работал, ввели OSGi, чтобы управлять демоном сложности в кодовой базе и ловить его в ловушки. OSGi не только не помог, но и сделал демона сложности гораздо могущественнее! Потребовалось несколько человеко-лет лучших разработчиков, чтобы всё переделать! Демон стал ещё сложнее, и теперь новые фичи стало невозможно реализовать! Очень плохо!
Забор Честертона
Мудрый шаман Честертон однажды сказал:
Существует некий институт или закон; скажем, для простоты, забор или ворота, перегораживающие дорогу. Современный реформатор весело подходит к ним и говорит: «Я не вижу в этом пользы; давайте-ка уберём». На что более разумный реформатор поступит правильно, если ответит: «Если вы не видите в этом пользы, я уж точно не позволю вам это убирать. Идите и подумайте. А когда вы вернётесь и скажете мне, что вы видите пользу, тогда, возможно, я позволю вам это разрушить».
Многие опытные разработчики хорошо усвоили этот урок и не начинают выкорчёвывать код направо и налево, каким бы уродливым он ни казался.
Я понимаю, что все программисты в какой-то степени платоники и желают видеть в коде совершенство, подобное музыке сфер. Но в этом и кроется опасность: мир часто уродлив и неуклюж, и таким же должен быть и код.
Смирение нечасто свойственно умникам или тем, кто считает себя таковыми, да и мне тоже, но я часто обнаруживал, что подход «о, мне не нравится, как это выглядит, сейчас я это исправлю» приводил ко многим часам боли и страданий, а система не становилась лучше, а то и вовсе ухудшалась.
В начале своей карьеры я часто врывался в кодовую базу, дико размахивая дубинкой, и всё крушил. Я понял, что это нехорошо.
Я не говорю, что никогда не нужно улучшать систему — это было бы глупо, — но я рекомендую сначала потратить время на то, чтобы понять её, особенно если система большая, и уважать код, который работает сегодня, даже если он не идеален.
Здесь тесты часто служат хорошей подсказкой, почему этот «забор» не стоит ломать!
Микросервисы
Я никак не пойму, зачем умники берут самую сложную проблему — правильное разделение системы на части — и добавляют к ней ещё и сетевые вызовы.
Мне это кажется очень запутанным.
Инструменты
Я люблю инструменты. Инструменты и контроль над страстями — вот что отличает человека от динозавров! Инструменты позволяют моему мозгу создавать код, который иначе был бы невозможен, потому что они думают за меня, а это всегда облегчение! Попадая в новое место, я всегда трачу время на изучение окружающих инструментов, чтобы максимизировать производительность. Две недели изучения инструментов часто ускоряют разработку вдвое, хотя для этого приходится копаться и просить помощи у других разработчиков, ведь документации нет.
Автодополнение кода в IDE позволяет мне не помнить все API, что очень важно!
Для меня программирование на Java без этого было бы почти невозможным!
Это действительно заставляет задуматься.
Хороший отладчик стоит всех блестящих камушков в мире, и даже больше. Столкнувшись с багом, я бы часто отдал все свои камушки и, возможно, пару детей за хороший отладчик. К тому же, насколько я знаю, отладчик ничего не весит.
Я всегда рекомендую начинающим программистам очень глубоко изучать доступный им отладчик. Такие функции, как условные точки останова, вычисление выражений, навигация по стеку вызовов и т.д., часто учат новичка о компьютерах больше, чем университетский курс!
Я считаю, что никогда не стоит прекращать улучшать свои инструменты.
Системы типов
Мне очень нравятся системы типов, они облегчают программирование. Для меня главная ценность систем типов в том, что я нажимаю точку на клавиатуре, и волшебным образом появляется список того, что я могу сделать. Это 90% или даже больше всей пользы от системы типов.
Шаманы-умники из мира систем типов часто говорят, что корректность типов — это главная цель, но я заметил, что некоторые из этих шаманов нечасто выпускают работающий код. Полагаю, код, который никогда не был выпущен, в каком-то смысле корректен, но это не то, что я имею в виду под корректностью.
Я говорю, что волшебное всплывающее окно с подсказками и автодополнением кода — это основное преимущество системы типов. Корректность — это тоже хорошо, но далеко не так важно.
И ещё, здесь нужно остерегаться умников!
Некоторые из них мыслят системами типов и говорят леммами — это потенциальная опасность!
Опасность заключается в слишком высоком уровне абстракции. Код, написанный таким умником, становится астральной проекцией платоновской обобщённой модели вычислений Тьюринга в кодовую базу. Я в замешательстве и согласен, что на каком-то уровне это очень элегантно, но также очень трудно сделать что-то практическое, например, учесть количество дубинок на складе для компании «Груг Инкорпорейтед».
Дженерики (обобщённое программирование) здесь особенно опасны. Я стараюсь ограничивать их использование в основном контейнерными классами, где они приносят наибольшую пользу.
Соблазн использовать дженерики очень велик — это ловушка! Демон сложности обожает этот трюк! Будьте осторожны!
Всегда помните: самая большая польза от системы типов — нажать точку и увидеть, что можно сделать!
Сложность выражений
Когда-то я любил минимизировать количество строк кода, насколько это возможно. Писал код вот так:
if(contact && !contact.isActive() && (contact.inGroup(FAMILY) || contact.inGroup(FRIENDS))) {
// ...
}
Со временем я понял, что это трудно отлаживать, и научился предпочитать такой стиль:
if(contact) {
var contactIsInactive = !contact.isActive();
var contactIsFamilyOrFriends = contact.inGroup(FAMILY) || contact.inGroup(FRIENDS);
if(contactIsInactive && contactIsFamilyOrFriends) {
// ...
}
}
Я слышу крики ужаса от молодых разработчиков при виде такого количества строк кода и бессмысленных переменных и готовлюсь защищаться дубинкой.
Начинается драка с другими разработчиками, и я кричу: «Так легче отлаживать! Можно ясно видеть результат каждого выражения и дать ему хорошее имя! Легче понять условное выражение! ЛЕГЧЕ ОТЛАЖИВАТЬ!»
Это определённо легче отлаживать, и как только драка заканчивается, все успокаиваются, и молодые разработчики, немного подумав, понимают, что я прав.
Я и сам до сих пор ловлю себя на том, что пишу код в первом стиле, и часто жалею об этом, так что я не осуждаю молодёжь.
DRY
DRY означает «Не повторяйся» (Don’t Repeat Yourself) — это мощный принцип, владеющий умами большинства разработчиков.
Я уважаю DRY, это хороший совет. Однако я рекомендую во всём соблюдать баланс, как советовал величайший из умников, Аристотель.
Я нашёл забавный график от Лии Веру, который соответствует моему отношению к повторениям:
За последние десять лет программирования я стал меньше беспокоиться о повторении кода. Пока повторяющийся код достаточно прост и очевиден, я начинаю чувствовать, что копирование кода с небольшими изменениями лучше, чем множество колбэков/замыканий, передаваемых в качестве аргументов, или сложная объектная модель. Иногда это слишком сложно и даёт слишком мало преимуществ.
Здесь трудно найти баланс. Повторяющийся код всё ещё заставляет меня остановиться и сказать «хммм», но опыт показывает, что иногда повторение лучше, чем сложное DRY-решение.
Примечание! Я призываю слишком буквальных разработчиков не воспринимать всерьёз строку «работает, значит, не трогай» — это шутка.
Разделение ответственностей (SoC)
Разделение ответственностей (Separation of Concerns, SoC) — ещё одна мощная идея, владеющая умами многих разработчиков. Её суть в том, чтобы разделять различные аспекты системы на отдельные участки кода.
Канонический пример из веб-разработки: разделение стилей (CSS-файл), разметки (HTML-файл) и логики (JavaScript-файл).
Здесь я настроен гораздо более скептически, чем в случае с DRY, и даже написал заумное эссе об альтернативном принципе проектирования — локальности поведения (locality of behavior, LoB) — в противовес SoC.
Я предпочитаю размещать код на том элементе, который выполняет действие. Теперь, когда я смотрю на этот элемент, я знаю, что он делает. Это всегда приносит облегчение!
При разделении ответственностей мне часто приходится бегать по множеству файлов, чтобы понять, как работает какая-то кнопка. Это очень сбивает с толку и отнимает время. Плохо!
Замыкания (Closures)
Мне нравятся замыкания для подходящих задач, и обычно эта задача — абстрагирование операций над коллекцией объектов.
Я предупреждаю: замыкания, как соль, системы типов и дженерики — небольшое количество очень полезно, но слишком большое может всё испортить и вызвать сердечный приступ.
JavaScript-разработчики называют особого демона сложности в JavaScript «адом колбэков» (callback hell), потому что JavaScript-библиотеки используют слишком много замыканий. Это очень печально, но, честно говоря, JavaScript-разработчики получили то, что заслужили.
Логирование (Logging)
Я большой поклонник логирования и призываю делать его как можно больше, особенно в облачных развёртываниях. Некоторые говорят, что логирование — это дорого и неважно. Раньше я тоже так думал, но больше нет.
Забавная история: я узнал, что мой кумир Роб Пайк работает над логированием в Google, и решил: «Если сам Роб Пайк занимается логированием, то что мне там делать?!» — и не стал пробовать устроиться. Оказалось, что логирование очень важно для Google, и, конечно же, над ним работают лучшие программисты!
Не будь таким простофилей, как я, теперь у меня гораздо меньше блестящих камушков!
Ну да ладно, я всё равно попал в хорошую компанию, а манера одеваться у Роба Пайка становится всё более эксцентричной, так что в итоге всё сложилось хорошо. Но суть остаётся: логирование очень важно!
Мои советы по логированию:
- Логируйте все основные логические ветвления в коде (if/for).
- Если «запрос» проходит через несколько машин в облачной инфраструктуре, включайте ID запроса во все логи, чтобы их можно было сгруппировать.
- Если возможно, сделайте уровень логирования динамически управляемым, чтобы можно было включать/выключать его при отладке проблем (а их много!).
- Если возможно, сделайте уровень логирования настраиваемым для каждого пользователя, чтобы можно было отлаживать проблемы конкретного пользователя.
Последние два пункта — особенно удобная дубинка в борьбе с багами на продакшене.
К сожалению, библиотеки для логирования часто очень сложны (Java, ну почему ты такая?), но стоит потратить время на то, чтобы настроить инфраструктуру логирования «как надо». По моему опыту, это окупится с лихвой.
Я думаю, логированию нужно больше учить в университетах.
Параллелизм (Concurrency)
Я, как и любой здравомыслящий разработчик, боюсь параллелизма.
Насколько это возможно, я стараюсь полагаться на простые модели параллелизма, такие как обработчики веб-запросов без состояния (stateless) и простые очереди удалённых задач, где задачи не зависят друг от друга и имеют простой API.
Оптимистичный параллелизм (optimistic concurrency) хорошо работает для веба.
Иногда я прибегаю к локальным переменным потока (thread local variable), обычно при написании кода для фреймворков.
В некоторых языках есть хорошие параллельные структуры данных, например, ConcurrentHashMap
в Java, но всё равно требуется осторожная работа, чтобы всё сделать правильно.
Я никогда не использовал Erlang, слышал о нём хорошие вещи, но язык выглядит странно, извините.
Оптимизация (Optimizing)
Величайший из умников однажды сказал:
Преждевременная оптимизация — корень всех зол.
Это знают почти все, и я со всей своей скромностью яростно согласен с этим величайшим умником.
Я рекомендую всегда иметь конкретный, реальный профиль производительности, указывающий на конкретную проблему, прежде чем начинать оптимизацию.
Никогда не знаешь, в чём на самом деле проблема. Я часто удивляюсь! Очень часто!
Остерегайтесь зацикливаться только на CPU. Легко видеть процессор и много думать о нотации «O-большое», которой учили в университете, но часто это не является корнем всех тормозов, к удивлению многих, включая меня.
Обращение к сети эквивалентно многим, многим миллионам циклов CPU, и его всегда следует минимизировать, если это возможно. Запомните это, умники-микросервисники!
Неопытный умник-разработчик видит вложенный цикл и часто говорит: «O(n^2)? Только не в мою смену!»
Демон сложности улыбается.
API
Я люблю хорошие API. Хорошие API не заставляют меня слишком много думать.
К сожалению, многие API очень плохие и заставляют меня думать довольно много. Это происходит по многим причинам, вот две из них:
- Создатели API мыслят в терминах реализации или предметной области API, а не в терминах его использования.
- Создатели API мыслят слишком абстрактно и заумно.
Обычно мне не важны детали API: я хочу записать файл, или отсортировать список, или что-то ещё, и просто хочу вызвать write()
или sort()
.
Но умники-разработчики API говорят:
«Не так быстро, парень! А файл открыт для записи? А ты определил Comparator для этой сортировки?»
Я снова сдерживаю руку, тянущуюся к дубинке.
Мне сейчас не до этого, мистер Умник, я просто хочу отсортировать список и записать файл!
Я признаю, что у дизайнеров API есть своя правда и что иногда эти вещи имеют значение, но часто — нет. Умникам-разработчикам API следовало бы проектировать простые API для простых случаев и делать сложные случаи возможными с помощью более сложных API.
Я называю это «слоистыми» API: два или три разных API на разных уровнях сложности для различных нужд.
Кроме того, если вы используете объектно-ориентированный подход, размещайте API на самом объекте, а не где-то ещё. Java в этом плане — худшая!
Я хочу отфильтровать список в Java.
«А ты преобразовал его в stream?»
Хорошо, я преобразую его в stream.
«ОК, теперь можешь фильтровать».
ОК, но теперь мне нужно вернуть список! А у меня stream!
«Ну, а ты собрал свой stream в список?»
Что?
«Определи Collector<? super T, A, R>
, чтобы собрать твой stream в список».
Тут я клянусь на могиле предков, что забью дубинкой каждого в этой комнате, но вместо этого считаю до двух и сохраняю спокойствие.
Поместите обычные вещи вроде filter()
прямо на список и сделайте так, чтобы он возвращал список, слушайте внимательно, умники-разработчики Java API!
Никто не знает о вашем «stream» и никогда о нём не слышал, это не сетевой API, все Java-разработчики используют списки, мистер Умник!
Парсинг (Parsing)
Я обожаю создавать языки программирования по любому поводу и считаю рекурсивный спуск самым увлекательным и красивым способом создания парсера.
К сожалению, во многих умных школах учат только инструментам-генераторам парсеров. Здесь моя обычная любовь к инструментам даёт сбой: генераторы парсеров создают ужасное змеиное гнездо кода. Его невозможно понять, он работает снизу вверх, что? Он скрывает рекурсивную природу грамматики и делает отладку невозможной. Очень плохо, по-моему!
Я думаю, это потому, что, хотя демон сложности вреден для кодовой базы и понимания, он очень хорош для написания множества академических статей. Печально, но факт.
Промышленные парсеры почти всегда используют рекурсивный спуск, несмотря на то, что школы его игнорируют! Я был в ярости, когда узнал, насколько прост парсинг! Парсинг — это не магия только для умников: вы тоже так можете!
Я был очень рад обнаружить, что умник-разработчик Боб Нистром реабилитировал племя умников и написал отличную книгу о рекурсивном спуске: «Создаём интерпретаторы» (Crafting Interpreters).
Книга доступна онлайн бесплатно, но я настоятельно рекомендую всем заинтересованным разработчикам купить её из общих принципов. Она даёт много умных советов, и я очень люблю эту книгу, за исключением паттерна «Посетитель» (это ловушка!).
Паттерн «Посетитель» (Visitor Pattern)
Фронтенд-разработка (Front End Development)
Некоторые разработчики, сталкиваясь с веб-разработкой, говорят:
«Я знаю! Я разделю свой фронтенд и бэкенд и буду использовать новомодную SPA-библиотеку, которая общается с бэкендом через GraphQL JSON API по HTTP (что забавно, потому что я не передаю гипертекст)».
Теперь у вас два логова демона сложности.
И, что ещё хуже, демон сложности на фронтенде ещё более могущественен и, насколько я могу судить, имеет глубокое духовное влияние на всю фронтенд-индустрию.
Бэкенд-разработчики стараются сохранять простоту, и у них это может получиться, но фронтенд-разработчики всё очень быстро усложняют и вводят кучу кода, питая демона сложности.
Даже когда сайту нужно всего лишь отправить форму в базу данных или это простой сайт-визитка!
Сейчас все так делают!
Я не уверен, почему, кроме как, может быть, потому что так сказали Facebook и Google, но для меня это не очень веская причина.
Мне не нравятся большие и сложные фронтенд-библиотеки, которые все используют.
Чтобы избежать этого, я создал htmx и hyperscript.
Они помогают поддерживать низкую сложность, использовать простой HTML и избегать большого количества JavaScript — естественного эфира для демона сложности.
Может быть, они вам подойдут, но вакансий по ним нет, извините.
React лучше для поиска работы и для некоторых типов приложений, но с ним вы становитесь послушником демона сложности, нравится вам это или нет. Извините, такова жизнь фронтендера.
Мода (Fads)
Я заметил, что в разработке много модных течений, особенно сегодня во фронтенде.
Бэкенд более скучный, потому что на данный момент там, кажется, уже перепробовали все плохие идеи (хотя некоторые всё ещё пытаются повторить!).
Во фронтенде всё ещё пробуют все плохие идеи, поэтому там много изменений, и трудно что-то понять.
Я рекомендую относиться ко всем революционным новым подходам с долей скепсиса: умники уже давно работают над компьютерами, и большинство идей уже хотя бы раз пробовали.
Я не говорю, что нельзя научиться новым трюкам или что нет хороших новых идей, но много времени тратится на переработку старых плохих идей. Большая часть силы демона сложности исходит от бездумного внедрения новых идей в кодовую базу.
Страх выглядеть глупо (Fear Of Looking Dumb)
Примечание! Очень хорошо, если старший разработчик готов публично сказать: «Хммм, это слишком сложно для меня»!
Многие разработчики боятся выглядеть глупо (Страх Выглядеть Глупо, СВГ). Я тоже когда-то боялся, но научился это преодолевать. Очень важно, чтобы старший разработчик говорил: «Это слишком сложно и непонятно для меня».
Это даёт младшим разработчикам разрешение признать, что им тоже сложно и они не понимают, что часто так и есть! СВГ — главный источник власти демона сложности над разработчиками, особенно над молодыми!
Лишите СВГ его силы, это очень хороший поступок для старшего разработчика!
Примечание: важно при этом сделать задумчивое лицо и выглядеть умным. Будьте готовы к тому, что какой-нибудь умник или, что хуже и гораздо чаще, тот, кто считает себя умником, отпустит ехидное замечание в ваш адрес.
Будьте сильными! Не бойтесь!
Дубинка здесь иногда полезна, но чаще помогает чувство юмора и, особенно, напоминание о последнем провальном проекте этого умника. Так что собирайте компромат и сохраняйте спокойствие.
Синдром самозванца (Impostor Syndrome)
Я заметил, что в разработке многие чувствуют себя самозванцами.
Я нахожусь в одном из двух состояний: либо я — властелин всего сущего, размахивающий дубинкой-кодом, как Тор, либо я понятия не имею, что делаю.
Большую часть времени я нахожусь во втором состоянии, хотя и неплохо это скрываю.
Я создал программное обеспечение, которое требует много работы и имеет умеренный успех в опенсорсе, и тем не менее я сам часто чувствую, что понятия не имею, что делаю! Очень часто! Я всё ещё боюсь совершить ошибку, которая сломает код у всех и разочарует других разработчиков. Самозванец!
Возможно, для большинства из нас это в природе программирования — чувствовать себя самозванцем, и лучшее, что можно сделать, — это смириться. Никто не самозванец, если все самозванцы.
Любой молодой разработчик, дочитавший до этого места, вероятно, сделает хорошую карьеру, даже если разочарования и беспокойство всегда будут рядом, извините.
Что почитать
Мне нравятся эти статьи:
- Worse is Better (Хуже — значит лучше)
- Worse is Better is Worse («Хуже — значит лучше» — это хуже)
- Is Worse Really Better? (Действительно ли хуже — это лучше?)
- A Philosophy of Software Design (Философия программного дизайна)
Заключение
А теперь скажите вы: сложность — это очень, очень плохо.