Комментарии 18
да, есть разные методики и практики, как написать хороший код, но это должно быть уместно, как бы это странно не звучало. Для большого сложного проекта следовать всем лучшим методикам — нормально и единственно разумно, потерями на усложнение структуры можно пренебречь, получив взамен больше ясности, разделённую логику, юнит тесты и всё вот это
вот, например, «привет, мир!» на php:
<?php
echo 'Hello, World!';
?>
Мы можем посмотреть на него, сказать, «что за процедурный кусок примитива» и прикрутить, например, ООП, создать интерфейс, класс, юнит тест, чтобы при выполнении терять кучу памяти и ресурсов на ООП-структуру, вызовы и её обслуживание.
Вопрос в том — нужно ли это, уместно ли это? Здесь нужно опираться на опыт, на планирование.
Вырастет ли ваш проект? Будет ли у него АПИ? Будут ли его части дописывать другие команды?
Потому что по аналогии можно любой самый простой пример нафаршировать всем, чем умные книжки посоветуют и получить в итоге ситуацию с мемасика, где на одного работника бухгалтер, директор, заместитель, сотрудник отдела кадров и уборщица)
Однозначно сказать когда их нужно применять сложно — нужен опыт который даст «чутье».
Для себя считаю что такие практики нужны когда кодовая база подкрадывается к 50000 строк и разработчиков более одного.
Другая метрика — это наличие roadmap проекта. Если он запланирован на год вперед, в планах расширять штат разработчиков, тогда стоит задуматься над этими практиками.
В тоже время, есть история с микросервисами. Там все эти шаблоны и стандарты лежат в другой плоскости, там уже больше решений в плане «делать асинхронно или синхронно?», «какими данными владеет сервис?» и так далее. Но на уровне 1 сервиса, если это действительно «микро» (тоже субъективно, но допустим до 10к строк), можно обойтись процедурным программированием и применение SOLID, чистой архитектуры, паттернов — спорно.
В продерном подходе нет ничего плохого, просто некоторые решение проще построить на ООП, где-то возможно — ФП.
Я не смотрю на проект по принципу — написано без применения ООП — не поддерживается. Нужно смотреть код. Если читается легко и структура понятна, то пусть хоть на на чем угодно будет написан, главное что это оправдано. =)
Но по-моему, пример после рефакторинга не стал понятнее, или гибче. Посчитайте, сколько классов придется затронуть при добавлении нового параметра (уровня солнечной активности, например). Что будет, если часть информации придется брать с другого сайта с другим форматом?
Хороший эксперимент, душок пропал, но читаемость снизилась, расширяемость не добавилась.
Так-как я пока безработный, самому хочется эту же задачку расписать) но не обещаю)
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, но не люблю подобные вещи (преобразованин даты в строку) писать в процедурном стиле.
А как вы нагрешили в код?
И тут не ясно… То ли вкусы не совпали, ты ли я реально всё переусложняю, то ли они быдлокодеры-олимпиадники)
Так от требований энтерпрайза оно так становится. Нужна API, на которую потенциально в будущем придут правки, которое должно быть оттестировано, дабы не потерять миллионы долларов из-за простой ошибки, чтобы раскатать этот функционал где-нибудь в облаках внутри докеров-кубернетисов, изолировать его от остальных сервисов на случай если злые хакеры вломятся в терминал с именной этой API, ну и соотвественно чтобы все это дело еще и не падало если упадет какой-то из сервисов на который опирается наш разворачиваемый API и еще писало какие-то осмысленные логи про ошибки/падения.
Это какой-то карго-культ уже.
"Here's what happens — granpa knows every damn word, doesn't understand a single one!" ©
Принцип ведь простой же — инструмент/практику/парадигму имеет смысл применять, если она уменьшает стоимость поддержки кода и вероятность ошибок. 40 строк тривиального кода + смок тест в данной ситуации намного более понятны, проще и поддерживаемы, нежели ворох "паттернов" по SOLID с 100% покрытием через моки.
В больших же проектах — наоборот. По мере роста проекта появляется много дублирования кода, изначально тривиальные вещи становятся повторящимся бойлерплейтом и копипастой, что увеличивает и вероятность ошибок, и стоимость дальнейшей поддержки. И здесь SOLID вместе з другими хорошими практиками как раз и становятся теми инструментами, которые уменьшают вероятность ошибок и стоимость поддержки.
Инструмент, который хорошо работает в одной ситуации — не обязательно хорошо работает в другой. Да и смысл не в самом инструменте, а в том, чтобы работало хорошо.
Оверинжиниринг головного мозга