Как стать автором
Обновить

Комментарии 18

ну это действительно похоже на «оверинжиниринг»)
да, есть разные методики и практики, как написать хороший код, но это должно быть уместно, как бы это странно не звучало. Для большого сложного проекта следовать всем лучшим методикам — нормально и единственно разумно, потерями на усложнение структуры можно пренебречь, получив взамен больше ясности, разделённую логику, юнит тесты и всё вот это
вот, например, «привет, мир!» на php:
<?php
   echo 'Hello, World!';
?>

Мы можем посмотреть на него, сказать, «что за процедурный кусок примитива» и прикрутить, например, ООП, создать интерфейс, класс, юнит тест, чтобы при выполнении терять кучу памяти и ресурсов на ООП-структуру, вызовы и её обслуживание.
Вопрос в том — нужно ли это, уместно ли это? Здесь нужно опираться на опыт, на планирование.
Вырастет ли ваш проект? Будет ли у него АПИ? Будут ли его части дописывать другие команды?
Потому что по аналогии можно любой самый простой пример нафаршировать всем, чем умные книжки посоветуют и получить в итоге ситуацию с мемасика, где на одного работника бухгалтер, директор, заместитель, сотрудник отдела кадров и уборщица)
Как я и писал код распух до почти 300 строк кода в сумме, при том же количестве исполняемых строк кода, но пример этот скорее академический. Мемасик с единственным работником действительно подходит, надо было его брать как заглавный
Паттерны, «стандарты» — это все нужно применять тогда, когда они дадут пользу.
Однозначно сказать когда их нужно применять сложно — нужен опыт который даст «чутье».
Для себя считаю что такие практики нужны когда кодовая база подкрадывается к 50000 строк и разработчиков более одного.
Другая метрика — это наличие roadmap проекта. Если он запланирован на год вперед, в планах расширять штат разработчиков, тогда стоит задуматься над этими практиками.

В тоже время, есть история с микросервисами. Там все эти шаблоны и стандарты лежат в другой плоскости, там уже больше решений в плане «делать асинхронно или синхронно?», «какими данными владеет сервис?» и так далее. Но на уровне 1 сервиса, если это действительно «микро» (тоже субъективно, но допустим до 10к строк), можно обойтись процедурным программированием и применение SOLID, чистой архитектуры, паттернов — спорно.

В продерном подходе нет ничего плохого, просто некоторые решение проще построить на ООП, где-то возможно — ФП.

Я не смотрю на проект по принципу — написано без применения ООП — не поддерживается. Нужно смотреть код. Если читается легко и структура понятна, то пусть хоть на на чем угодно будет написан, главное что это оправдано. =)
На маленьком проекте — разницы большой нет. И тот и тот вариант приемлемы и не вызывают проблемы поддержки. На большом проекте я бы предпочел структурированную архитектуру и паттерны, а не большие методы.
Классная тема! Меня на днях из-за такого уволили)
Но по-моему, пример после рефакторинга не стал понятнее, или гибче. Посчитайте, сколько классов придется затронуть при добавлении нового параметра (уровня солнечной активности, например). Что будет, если часть информации придется брать с другого сайта с другим форматом?
Хороший эксперимент, душок пропал, но читаемость снизилась, расширяемость не добавилась.
Так-как я пока безработный, самому хочется эту же задачку расписать) но не обещаю)
Я бы сделал примерно так. Написал на функциях для краткости, но тестировать парсер можно и в таком виде без прокидывания зависимостей через конструктор. Убрал микрооптимизации типа генерации общего URL, query параметров. Эти вещи практически не нарушают DRY, даже в исходном варианте до рефакторинга это было преждевременно.

interface Measurement {
	date: Date;
	value: number;
}

interface Response {
	airTemperatures: Measurement[];
	waterTemperatures: Measurement[];
}

export class SimpleDate {
	constructor(private date: Date) {}

	/** Returns date in `DD.MM.YYYY` format  */
	toString(): string {
		return `${this.date.getDate()}.${this.date.getMonth() + 1}.${this.date.getFullYear()}`;
	}
}

async function getMeasurements(from: SimpleDate, to: SimpleDate): Promise<Response> {
	return {
		airTemperatures: await getAirTemperatureMeasurements(from, to),
		waterTemperatures: await getWaterTemperatureMeasurements(from, to),
	};
}

async function getAirTemperatureMeasurements(
	from: SimpleDate,
	to: SimpleDate,
): Promise<Measurement[]> {
	return parseMeasurements(await getHtmlContent(`/air-temperature?from=${from}&to=${to}`));
}

// Покрываем тестами вида "html" -> Measurements[]
// Для специфичных форматов будем создавать подобные парсеры
function parseMeasurements(html: string): Measurement[] {
	return []; // not implemented
}

async function getHtmlContent(url: string): Promise<string> {
	return 'html...'; // not implemented
}

async function getWaterTemperatureMeasurements(
	from: SimpleDate,
	to: SimpleDate,
): Promise<Measurement[]> {
	return parseMeasurements(await getHtmlContent(`/water-temperature?from=${from}&to=${to}`));
}



На мой взгляд, основная ошибка изначального рефакторинга — желание разбить код на слои. Слои получатся сами собой, не надо к ним стремиться. В первую очередь нужно думать про разделение кода по фичам, которые могут меняться, добавляться независимо друг от друга — SRP, OCP.

Возможно, я так же преждевременно вытащил класс SimpleDate, но не люблю подобные вещи (преобразованин даты в строку) писать в процедурном стиле.
Да вы правы, добавление нового измерения потребует добавить 8 строк в 6 файлах. Вы не разложили ваш код на файлы, но строк кода для нового измерения потребуется от 5, но скорее больше, а изначальный вариант обошёлся бы одной строкой. Это конечно странно меряться количеством строк, особенно когда речь идёт менее чем о 10, но это же только пример. P.S. Удачи вам в поисках работы
Представьте, что каждая функция в моем примере, это отдельный файл (отдельный класс). Основная метрика не в количестве строк кода. А в количестве классов, которые придется менять. В идеальном варианте при добавлении нового измерения ничего менять не надо, нужно только добавлять новые классы-файлы.
Думаю разделение на файлы может привнести немного больше сложности, чем кажется на первый взгляд, но в общем я с вами согласен

А как вы нагрешили в код?

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

И тут не ясно… То ли вкусы не совпали, ты ли я реально всё переусложняю, то ли они быдлокодеры-олимпиадники)
И самое страшное, что я до сих пор получаю уведомления к своим открытым ПР-ам. И прямо на глазах мой FizzBuzzEnterprise превращается в функцию из примера исходного кода в статье… Блин, чуваки шарят в садизме…
НЛО прилетело и опубликовало эту надпись здесь
Замечательный код, буду смеяться весь день, теперь точно не успокоиться. Как раз и хотелось понять почему и как простой FizzBuzz превразается в FizzBuzzEnterpriseEdition. Очевидно, что мы не стремимся к этому осознанно, но можем приблизиться.

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

Это какой-то карго-культ уже.
"Here's what happens — granpa knows every damn word, doesn't understand a single one!" ©


Принцип ведь простой же — инструмент/практику/парадигму имеет смысл применять, если она уменьшает стоимость поддержки кода и вероятность ошибок. 40 строк тривиального кода + смок тест в данной ситуации намного более понятны, проще и поддерживаемы, нежели ворох "паттернов" по SOLID с 100% покрытием через моки.


В больших же проектах — наоборот. По мере роста проекта появляется много дублирования кода, изначально тривиальные вещи становятся повторящимся бойлерплейтом и копипастой, что увеличивает и вероятность ошибок, и стоимость дальнейшей поддержки. И здесь SOLID вместе з другими хорошими практиками как раз и становятся теми инструментами, которые уменьшают вероятность ошибок и стоимость поддержки.


Инструмент, который хорошо работает в одной ситуации — не обязательно хорошо работает в другой. Да и смысл не в самом инструменте, а в том, чтобы работало хорошо.

Да, вы правы, это и демонстрируется в данном примере. Хотелось бы не забывать об этом работая над большими проектами. И почаще задумываться пройдена ли та граница, за которой начинается оверинжиниринг. Когда смотришь на FizzBuzzEnterpriseEdition, думаешь «с моим-то проектом такого не случится», а потом недоумеваешь.
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации