Стильный код и его последствия
AbanazarВы никогда не задумывались о том, что во время написания кода наш мозг использует и комбинирует два стиля? Там где мы устали — декларативный, а там где полны фантазий — императивный.
Давайте для наглядного примера напишем алгоритм вычисления факториала числа в обоих стилях на Golang:
func factorialImperative(n int) int {
result := 1
for i := 1; i <= n; i++ {
result *= i
}
return result
}
func factorialDeclarative(n int) int {
if n <= 1 {
return 1
}
return n * factorialDeclarative(n-1)
}
Во втором подходе мы используем рекурсивную функцию для определения факториала числа. Он является декларативным, так как описывает что такое факториал (рекурсивное умножение на число) без явного указания как это делать в цикле.
Отличие между этими двумя стилями проявляется в способе описания логики. В императивном стиле мы явно управляем состоянием и циклами, тогда как в декларативном стиле мы определяем логику и позволяем ей самой себя вызывать.
Императивный стиль:
* Описывает как выполнить определенную задачу шаг за шагом.
* Программист указывает, какие действия должны быть предприняты, чтобы достичь конечного результата.
* Часто использует циклы, условия и изменение состояния переменных.
Пример: использование циклов for и операторов if для управления выполнением задачи.
Декларативный стиль:
* Описывает что должно быть достигнуто, но не обязательно как это должно быть сделано.
* Программист определяет цель, а не шаги, необходимые для достижения этой цели.
* Основной акцент на выразительности и абстракциях.
* Часто использует функции и объявления, чтобы определить логику, но не меняет состояние программы напрямую.
Пример: использование функций высшего порядка для фильтрации данных, без явных циклов.
Теперь давайте обратимся к Python для ещё одного наглядного примера. Мы преобразуем числа из одного списка в новый список квадратов этих чисел.
Императивный стиль (как):
numbers = [1, 2, 3, 4, 5]
squared_numbers = []
for num in numbers:
squared = num * num
squared_numbers.append(squared)
Декларативный стиль (что):
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x * x, numbers))
Думаю теперь у вас сложилась полная картина в голове и отличия двух стилей стали явными. Почему я вообще начал копошится в этой фундаментально простой теме? Всё потому что я стал часто замечать смену двух стилей во время написания кода в своих проектах, а так же замечать тоже самое в коде моих коллег на работе.
Во-первых не всем всегда бывает понятна лаконичная абстракция по типу той же lambda функции.
Во-вторых после того как листаешь и читаешь императивный код, а потом внезапно натыкаешься на подобную декларативщину то не сразу мозг включается, и первые секунды ты усердно ничего не понимаешь.
Это создаёт некоторые проблемы даже в кодовой коммуникации в команде разработчиков. Не все коллеги в вашей команде понимают такие "речевые обороты" и абстракции. А те что понимают всё равно потратят время в раздумьях. Всё это не является какой-то острой проблемой, но я приверженец императивного стиля везде где это возможно. Я считаю что код, который в каждом своем фрагменте придерживается одного стиля является более понятным и читабельным чем смесь и/или перепады стилей. Но если мы возьмём полностью декларативный код с абсолютной "лаконичностью" и использованием синтаксического сахара то получим намного менее читабельный рассказ. Согласитесь, что легче воспринимать инструкции КАК нежели ЧТО. Компьютеру нет разницы, а вот для человека она весомая.
И ведь проблема то не на одном синтаксисе топчется, но ещё и на знаниях и понимании. Если разработчик не знает работы так сказать из под капота он так и продолжит везде пихать свои декларативные элементы. Этот стиль порой даже заставляет нас не знать, не понимать и бездумно применять. Всё это перестает быть индивидуальной проблемой с опытом конечно, но доставляет неприятностей к этому моменту.
Под декларативный стиль за счёт абстракции можно подписать и импортируемые библиотеки. Кто нибудь из вас открывал исходный код стандартной библиотеки net/http на Golang? А я вот открыл и удивился очевидному уровню абстрактной вложенности, ведь даже этот в какой-то мере низкоуровневый код импортирует другие модули. Мы бездумно год за годом на этом своем backend используем методы сетевых библиотек даже не зная как они работают и что делают на самом деле. Да, мы знаем теорию, но поверьте там внизу скрыт целый мир, который работает далеко не так очевидно как мы считаем. А что происходит глобально в индустрии? Каждый день кто-то пишет новые библиотеки и фреймворки, а мы их бесконечно потребляем, разработчики языков дают нам больше сахара и мы его тоже употребляем в больших количествах.
Я считаю излишним тот багаж декларативного изящества, который лежит у нас в руках. Думаю чем больше императивного кода и подходов в разработке тем лучше для каждого индивидуально и для индустрии глобально.
Вся эта статья не является призывом, а лишь моими личными заметками. На данный момент я размышляю над различными методами написания императивного кода, который будет таковым являться при любой глубине абстракции. По мере продвижения и личных открытий буду держать всех нас в курсе, но что-то мне подсказывает, что такое приключение не имеет результативного логического завершения.