Хабрахабр

[Перевод] Функциональное программирование: дурацкая игрушка, которая убивает производительность труда. Часть 1

Возможно, вы уже слышали о так называемом «функциональном» программировании. Возможно, вы даже подумываете о том, что вам стоит его как-нибудь попробовать.

Ни в коем случае этого не делайте!

Его применение приведёт к резкому падению производительности труда. Функциональное программирование полно недочётов, оно не подходит для реальных проектов. Давайте выясним.
Почему это так?

Функциональное программирование не может удовлетворить многогранным корпоративным требованиям

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

Но совсем скоро всё встанет на свои места. Полагаю, вышеприведённый текст особенно понятным не выглядит.

Дело в том, что оно основано на математических правилах (эти правила, что совершенно понятно, не находят применения в реальном мире, находящемся снаружи стен учебных заведений). Так называемое «функциональное» программирование не имеет нормального механизма создания абстракций. В отличие от ООП, функциональное программирование не делает никаких попыток пойти навстречу всем тем многочисленным, жёстким и сложным требованиям к ПО, которые предъявляют организации.

В следующем фрагменте кода продемонстрированы проблемы, широко распространённые там, где пользуются функциональным программированием:

import from 'lodash/fp'; const filterByType = type => filter( x => x.type === type ); const fruits = [ { type: 'apple', price: 1.99 }, { type: 'orange', price: 2.99 }, { type: 'grape', price: 44.95 } ]; const getFruitPrice = type => fruits => fruits |> filterByType(type) |> first |> get('price'); const getApplePrice = getFruitPrice('apple'); console.log('apple price', getApplePrice(fruits));

Если это всё ничего кроме злости у вас не вызывает, то знайте, что вы в этом не одиноки!

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

Если же кто-то это и сделает, то в любой нормальной большой компании его немедленно уволят, поступив так для того, чтобы его действия этой компании больше не навредили. Ни один уважающий себя программист никогда не будет писать подобный код! В следующем разделе мы взглянем на программу, написанную в стиле ООП, в которой всё абстрагировано так, как нужно.

Функциональные программные решения не выдерживают проверку временем

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

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

class Fruit { constructor(type, price) { this.type = type; this.price = price; }
} class AbstractFruitFactory { make(type, price) { return new Fruit(type, price); }
} class AppleFactory extends AbstractFruitFactory { make(price) { return super.make("apple", price); }
} class OrangeFactory extends AbstractFruitFactory { make(price) { return super.make("orange", price); }
} class GrapeFactory extends AbstractFruitFactory { make(price) { return super.make("grape", price); }
} class FruitRepository { constructor() { this.fruitList = []; } locate(strategy) { return strategy.locate(this.fruitList); } put(fruit) { this.fruitList.push(fruit); }
} class FruitLocationStrategy { constructor(fruitType) { this.fruitType = fruitType; } locate(list) { return list.find(x => x.type === this.fruitType); }
} class FruitPriceLocator { constructor(fruitRepository, locationStrategy) { this.fruitRepository = fruitRepository; this.locationStrategy = locationStrategy; } locatePrice() { return this.fruitRepository.locate(this.locationStrategy).price; }
} const appleFactory = new AppleFactory();
const orangeFactory = new OrangeFactory();
const grapeFactory = new GrapeFactory(); const fruitRepository = new FruitRepository();
fruitRepository.put(appleFactory.make(1.99));
fruitRepository.put(orangeFactory.make(2.99));
fruitRepository.put(grapeFactory.make(44.95)); const appleLocationStrategy = new FruitLocationStrategy("apple"); const applePriceLocator = new FruitPriceLocator( fruitRepository, appleLocationStrategy
); const applePrice = applePriceLocator.locatePrice(); console.log("apple", applePrice);

Как видите, вся основная функциональность здесь качественно абстрагирована. Этот — цельный код.

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

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

Серьёзный менеджмент нуждается в серьёзных возможностях

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

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

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

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

Использование этой методологии слишком сильно упрощает рефакторинг: Следующий пример ясно показывает неполноценность функционального программирования.

// до рефакторинга: // calculator.js:
const isValidInput = text => true; const btnAddClick = (aText, bText) => { if (!isValidInput(aText) || !isValidInput(bText)) { return; }
} // после рефакторинга: // inputValidator.js:
export const isValidInput = text => true; // calculator.js:
import { isValidInput } from './inputValidator'; const btnAddClick = (aText, bText, _isValidInput = isValidInput) => { if (!_isValidInput(aText) || !_isValidInput(bText)) { return; }
}

Если вас воротит от простоты этого рефакторинга — знайте, что такие ощущения испытываете не только вы. До рефакторинга было шесть строк кода, после стало семь строк? Это что — шутка?

Сравним это с нормальным рефакторингом объектно-ориентированного кода:

// до рефакторинга:
public class CalculatorForm { private string aText, bText; private bool IsValidInput(string text) => true; private void btnAddClick(object sender, EventArgs e) { if ( !IsValidInput(bText) || !IsValidInput(aText) ) { return; } }
} // после рефакторинга:
public class CalculatorForm { private string aText, bText; private readonly IInputValidator _inputValidator; public CalculatorForm(IInputValidator inputValidator) { _inputValidator = inputValidator; } private void btnAddClick(object sender, EventArgs e) { if ( !_inputValidator.IsValidInput(bText) || !_inputValidator.IsValidInput(aText) ) { return; } }
} public interface IInputValidator { bool IsValidInput(string text);
} public class InputValidator : IInputValidator { public bool IsValidInput(string text) => true;
} public class InputValidatorFactory { public IInputValidator CreateInputValidator() => new InputValidator();
}

Именно так выглядит настоящее программирование! До рефакторинга было девять строк кода, а после стало 22. Для выполнения этой работы требуется приложить больше усилий. Это заставит программистов, работающих на компанию, дважды подумать перед тем, как ввязываться в подобную авантюру.

Ущербная концепция декларативного подхода к программированию

Так называемые «функциональные» программисты неоправданно гордятся собой из-за того, что пишут декларативный код. Но тут нечем гордиться — подобный код лишь создаёт иллюзию продуктивности.

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

Взглянем на хорошо абстрагированный объектно-ориентированный код:

class CountryUserSelectionStrategy { constructor(country) { this.country = country; } isMatch(user) { return user.country === this.country; }
} class UserSelector { constructor(repository, userSelectionStrategy) { this.repository = repository; this.userSelectionStrategy = userSelectionStrategy; } selectUser() { let user = null; for (const u in users) { if ( this.userSelectionStrategy.isMatch(u) ) { user = u; break; } } return user; }
} const userRepository = new UserRepository();
const userInitializer = new UserInitializer();
userInitializer.initialize(userRepository); const americanSelectionStrategy = new CountryUserSelectionStrategy('USA');
const americanUserSelector = new UserSelector(userRepository, americanSelectionStrategy); const american = americanUserSelector.selectUser(); console.log('American', american);

Присмотритесь к императивному циклу for (const u in users). Не обращайте внимания на второстепенный шаблонный объектно-ориентированный код, не связанный с выполняемой задачей. Его нужно было включить в программу для того, чтобы сделать этот пример соответствующим жёстким требованиям абстрагирования, предъявляемым любой серьёзной организацией к коду.

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

SELECT * FROM Users WHERE Country=’USA’;

Каждый раз, когда я вижу SQL-код, меня прямо-таки коробит от его декларативности. Почему SQL? Почему бы программистам не использовать адекватные абстракции корпоративного класса и не писать бы нормальный объектно-ориентированный код? Особенно учитывая то, что в их распоряжении уже есть всё необходимое. Это — просто взрыв мозга, иначе и не скажешь.

Моделирование реального мира

Объектно-ориентированное программирование — это гениально. В отличие от «функционального» программирования оно отлично подходит для моделирования объектов реального мира. Это возможно благодаря тому, что ООП поддерживает такие продвинутые технологии, как наследование, полиморфизм и инкапсуляция.

Как уже было сказано, наследование отлично подходит для моделирования реального мира. Любой уважающий себя программист должен ежедневно использовать наследование для достижения высокого уровня многократного использования кода. Жизнь зародилась в океане несколько миллиардов лет назад. Кошки, например, всегда наследуют свои свойства и поведение от единственного абстрактного животного из реального мира. Например, говоря объектно-ориентированным языком, это может быть нечто вроде garfield.fishHead — свойства, описывающего рыбью голову кота Гарфилда. В результате все млекопитающие (включая кошек) унаследовали свойства от некоей первозданной рыбы. Никого не удивляет то, что кошки так сильно любят принимать ванны и плавать! То же самое можно сказать и о поведении, что, в терминологии ООП может выглядеть как garfield.swim() (плавание) и garfield.layCaviar() (икрометание). Человек, если захочет, легко может начать метать икру! Люди, в сущности, это то же самое.

Функциональное программирование неоправданно лишает программистов замечательных конструкций по совместному использованию кода, создателей которых вдохновила сама природа. Наши программы всегда должны следовать похожим иерархическим подходам к организации кода. У этого есть далеко идущие последствия — особенно если речь идёт о серьёзных сложных корпоративных приложениях.

Функции всегда должны быть привязаны к объектам

То, что функции должны быть всегда привязаны к объектам, подсказывает нам здравый смысл. Кроме того, это отлично воспроизводит то, что мы можем видеть в реальном мире. В блокнотах имеется встроенный метод «записать». Это метод вызывается каждый раз, когда вы планируете что-то записать в блокнот. Вы можете этого и не осознавать, но и у вас есть методы. Например — нечто вроде .eat(veggies), позволяющий вам есть овощи, и .doHomework(), благодаря которому вы делали домашние задания когда учились в школе. Это — просто здравый смысл. Как иначе ваша мама, когда вы были помладше, заставляла бы вас есть овощи и делать домашние задания? Конечно, она напрямую вызывала эти ваши методы!

Его можно представить в виде объекта Manager. Ни одну работу в реальном мире нельзя выполнить, не привлекая профессионального менеджера, координирующего задачи. Молодёжи, возможно, нужен менеджер, помогающий удовлетворять базовые человеческие потребности, ну, скажем, вроде «смотреть Netflix и отдыхать».

А люди достаточно умные могут нанять и несколько менеджеров, поступив в точности так, как рекомендует ООП. Кто, в конце концов, будет координировать все составные части этого сложного процесса?

У Леонардо да Винчи, например, была фабрика по производству картин — MonaLisaFactory, а Дональд Трамп строит секретную фабрику WallFactory. В реально мире создание чего-то нового и интересного, кроме того, требует специальной фабрики, которую можно представить в виде объекта Factory.

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

Функциональное программирование, очевидно, не подходит для любой серьёзной работы, ориентированной на реальный мир.

Функциональное программирование не даёт возможностей для профессионального роста

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

Затем нужно хорошо освоить тьму паттернов проектирования (вроде паттерна «Синглтон») и начать использовать их в своём коде. Во-первых, программисту нужно изучить продвинутые ООП-техники вроде наследования, абстракции, инкапсуляции и полиморфизма. В идеале где-то в процессе изучения паттернов программист должен начать использовать в своём коде различные абстракции корпоративного уровня. Существует около 30 базовых паттернов проектирования, которые нужно очень хорошо знать.

Кроме того, рекомендовано изучение подходящих инструментов для рефакторинга кода вроде Resharper, так как рефакторинг объектно-ориентированного кода — задача нетривиальная. Следующий шаг — ознакомление с технологиями наподобие Domain-Driven Design, и изучение того, как разделять на части монолитные программные проекты.

Но надо отметить, что даже большинство тех, у кого наберётся 30 лет опыта, не могут считаться настоящими мастерами ООП. Для того чтобы достичь достойного уровня в сфере ООП нужно 20-30 лет. Объектно-ориентированного разработчика, в результате, ждёт учёба длиною в жизнь. Путь ООП-ученика тяжёл и наполнен неопределённостью. Это ли не прекрасно?

К сожалению, им нужно изучить не так уж и много. А как насчёт несчастных функциональных программистов? У них начало неплохо получаться примерно через полгода. Я сам учил нескольких джуниоров функциональному программированию на JavaScript. Где тут упоение от пожизненной учёбы? Им просто понадобилось понять несколько базовых концепций, а затем надо было научиться быстро их применять. Я бы им не позавидовал.

Продолжение следует…

Уважаемые читатели! Что вам больше всего не нравится в функциональном программировании?

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»