Код как живой организм: от говнокода к архитектуре
Как страх «плохого кода» мешает развиваться
Когда я только начинал программировать, мне казалось, что хороший код должен выглядеть идеально с первой строчки:
Я боялся браться за задачи, потому что думал:
«А вдруг сделаю не по SOLID? А если нарушу DRY? А если опытные разработчики увидят мой код и назовут его говнокодом?»
Результат? Паралич. Вместо кода — пустой файл, бесконечные раздумья и ноль результата.
Код — как живой организм. Он рождается слабым, обрастает мускулами и только потом становится сильным.
Почему первый код — это почти всегда «говнокод» (и это не плохо)
Working solution > Perfect architecture
Твоя задача — не написать шедевр, а решить проблему.
Пример: функция на Clojure
Допустим, нам нужно отфильтровать список чисел, оставив только чётные.
Первый вариант (наивный, «говнокод»):
(defn filter-even [numbers] (loop [nums numbers result []] (if (empty? nums) result (let [num (first nums)] (if (even? num) (recur (rest nums) (conj result num)) (recur (rest nums) result)))))
Но это работает! И это главное.
3. Эволюция кода: как хаос превращается в порядок
Теперь, когда есть работающее решение, можно его улучшать.
Рефакторинг #1: используем filter
(defn filter-even [numbers] (filter even? numbers))
Уже лучше! Но что, если нужно ещё и удвоить чётные числа?
Рефакторинг #2: добавляем map
(defn process-numbers [numbers] (->> numbers (filter even?) (map #(* % 2))))
- Мы не планировали эту архитектуру заранее.
- Она появилась естественно, когда потребовалось добавить новую логику.
4. Почему паттерны и SOLID — это результат, а не старт
Многие думают, что нужно сначала выучить все принципы, а потом писать код.
- Ты пишешь как получается,
- Сталкиваешься с проблемами,
- И только потом осознаёшь, что твой код — это, например, Стратегия или Фабрика.
Пример: «случайный» паттерн
(defn save-data [data storage-type] (cond (= storage-type :db) (save-to-db data) (= storage-type :file) (save-to-file data)))
Какие проблемы возникают?
1. Трудно масштабировать
Допустим, появился новый тип хранилища — :s3
. Теперь нужно:
Проблема: Нарушение Open-Closed Principle (код открыт для изменений, но закрыт для модификаций).
2. Жёсткая связанность
Функция save-data
явно зависит от:
Сложно тестировать
Чтобы протестировать save-data
, нужно:
Позже мы понимаем, что это Strategy — и выносим логику в отдельные функции:
(defmulti save-data (fn [data storage-type] storage-type)) (defmethod save-data :db [data _] (save-to-db data)) (defmethod save-data :file [data _] (save-to-file data))
Как прийти к этому решению на практике?
- Сначала пишем «лобовой» код через
cond
/case
(это нормально!). - Замечаем, что веток становится слишком много.
- Рефакторим, когда:
Есть список пользователей, и нужно:
1. Первая мысль: «Сделаю через циклы!» (потому что это «очевидно»)
(defn get-active-usernames [users] (let [active-users (loop [users users result []] (if (empty? users) result (let [user (first users)] (if (:active? user) (recur (rest users) (conj result user)) (recur (rest users) result))))) usernames (loop [users active-users result []] (if (empty? users) result (recur (rest users) (conj result (:name (first users)))))) sorted-names (sort usernames)] sorted-names))
- Мозг сразу тянется к императивному подходу («перебрать, проверить, собрать»).
- Страх перед «неизвестными» функциями (
filter
,map
,comp
). - Кажется, что «так надёжнее» (хотя код превращается в лапшу).
2. Момент осознания: «А ведь это можно упростить…»
После того как код работает, появляется мысль:
«Наверное, есть способ лучше» → гуглим/спрашиваем/экспериментируем.
Шаг 1. Замена ручного фильтра на filter
(defn get-active-usernames [users] (let [active-users (filter :active? users) ; <- магия keyword как предиката! usernames (map :name active-users) sorted-names (sort usernames)] sorted-names))
Шаг 2. Убираем промежуточные переменные
(defn get-active-usernames [users] (sort (map :name (filter :active? users))))
Шаг 3. Тред-ласт макрос (->>
) для читаемости
(defn get-active-usernames [users] (->> users (filter :active?) (map :name) (sort)))
Психологический подтекст
- Страх «незнания»
- Сначала кажется, что
loop/recur
— это «безопасно», потому что понятно. - Функции высшего порядка (
filter
,map
) требуют доверия к языку. - Эффект «Я это сделал!»
- После рабочего, но громоздкого кода, приходит облегчение.
- Теперь можно сфокусироваться на улучшениях (а не на панике).
- Архитектура через рефакторинг
Индустрия программирования создаёт токсичный перфекционизм
Курсы, собеседования, статьи — все твердят:
- «SOLID, паттерны, чистый код!»
- «Если не знаешь DRY/KISS/YAGNI — ты не разработчик!»
- «Архитектура должна быть идеальной с первого коммита».
Результат?
Новички (и даже мидлы) впадают в паралич анализа:
- «Вдруг я сделаю не по стандартам?»
- «А что если мой код назовут говнокодом?»
- «Я не понимаю, как применить Factory Method здесь… Может, не браться за задачу?»
Лучшие практики — это выводы, а не входные данные.
Их нельзя «выучить». Их можно только прочувствовать через боль.
Почему «быстрый говнокод» — это не стыдно, а необходимо
Пример из жизни
Представь, что ты учишься играть на гитаре:
- Теория: «Вот 100 аккордов, вот 20 гамм, играй как Хендрикс».
- Реальность: Сначала ты бренчишь «Кузнечика» на трёх аккордах — и это нормально.
То же с кодом. Алгоритм эффективного обучения:
- Накидал прототип (пусть криво, но работает).
- Понял задачу (ага, вот где подводные камни).
- Улучшил (рефакторинг, паттерны, оптимизация).
Почему Clojur-разработчикам тут повезло
- ФП + иммутабельность = меньше последствий от хаотичного прототипирования.
- REPL = моментальная проверка гипотез без «перезапуска проекта».
- Парадигма «данные > функции» = легко пересобирать логику.
3. Как бороться со страхом «неидеального кода»
Принять: первый код почти всегда плох
Твой код — черновик, а не высеченный в мраморе памятник.
Отделяй «эксперимент» от «продакшна»
- Создайте ветку
shitcode-prototype
. - Пиши в ней как угодно, чтобы проверить гипотезу.
- Если идея сработала — тогда рефакторь.
Спрашивай не «Как сделать идеально?», а «Как сделать сейчас?»
- Плохо: «Я не знаю, как применить Command Pattern здесь…»
- Хорошо: «Сначала сделаю через
cond
, потом посмотрю, что можно улучшить».
4. Что говорят практики
Кен Бек (автор TDD):
«Сначала сделайте так, чтобы оно работало. Затем сделайте правильно. И только потом — быстро».
Роберт Мартин (Clean Code):
«Единственный способ написать чистый код — сначала написать грязный, а потом почистить его».
Clojure-разработчик в дикой природе:
«80% моего кода начинаются как(->> data (map ...) (filter ...) (грубая агрегация))
.
А потом я удаляю половину и получаю хорошее решение».
Вывод: принцип «Сначала практика, потом теория»
- Курсы/книги учат «как должно быть», но не «как дойти до этого».
- Твой опыт важнее чем мнение «экспертов».
- Clojure — идеальный язык, чтобы быстро ваять прототипы и виртуозно рефакторить.
- Намеренно напиши максимально «плохой» код для задачи.
- Заставь его работать.
- Затем улучши.
- Почувствуй разницу!
Вывод: кайфуй от процесса, а не гонись за идеалом
- Не бойся писать плохой код — это этап, а не приговор.
- Архитектура рождается в процессе — а не в вакууме.
- Паттерны — результат опыта — а не его условие.
Первый вариант кода — это не «плохо», это исследование.
Мы не пишем архитектуру — мы её открываем в процессе.
Сначала — работающий код. Потом — красивый. И только затем — «идеальный» (если он вообще нужен).