Русский
Русский
English
Статистика
Реклама

Kiss

Перевод Принципы для разработки KISS, DRY, YAGNI, BDUF, SOLID, APO и бритва Оккама

10.03.2021 18:12:48 | Автор: admin
image

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

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

Последовательное применение этих принципов упростит ваш переход от миддла к сеньору. Вы можете обнаружить, что некоторые (вероятно) вы применяете интуитивно.

Принципов много. Мы остановимся на семи самых важных. Их использование поможет вам в развитии и позволит стать лучшим программистом.

1. YAGNI

You Arent Gonna Need It / Вам это не понадобится

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

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

Может наступить день, когда они снова понадобятся тогда вы сможете воспользоваться git-репозиторием, чтобы воскресить их из мертвых.

2. DRY

Dont Repeat Yourself / Не повторяйтесь

Эта концепция была впервые сформулирована в книге Энди Ханта и Дэйва Томаса Программист-прагматик: путь от подмастерья к мастеру.

Идея вращается вокруг единого источника правды (single source of truth SSOT). Что это вообще такое?

В проектировании и теории информационных систем единый источник истины (SSOT) это практика структурирования информационных моделей и схемы данных, которая подразумевает, что все фрагменты данных обрабатываются (или редактируются) только в одном месте SSOT предоставляют достоверные, актуальные и пригодные к использованию данные.

Википедия


Использование SSOT позволит создать более прочную и понятную кодовую базу.

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

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

3. KISS

Keep It Simple, Stupid / Будь проще

Этот принцип был разработан ВМС США в 1960 году. Этот принцип гласит, что простые системы будут работать лучше и надежнее.

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

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

Иногда самое разумное решение оказывается и самым простым. Написание производительного, эффективного и простого кода это прекрасно.

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

4. Big Design Up Front

Глобальное проектирование прежде всего

Этот подход к разработке программного обеспечения очень важен, и его часто игнорируют. Прежде чем переходить к реализации, убедитесь, что все хорошо продумано.

Зачастую продумывание решений избавляло нас от проблем при разработке Внесение изменений в спецификации занимало час или два. Если бы мы вносили эти изменения в код, на это уходили бы недели. Я даже не могу выразить насколько сильно я верю в важность проектирования перед реализацией, хотя адепты экстремального программирования предали эту практику анафеме. Я экономил время и делал свои продукты лучше, используя BDUF, и я горжусь этим фактом, чтобы там ни говорили фанатики экстремального программирования. Они просто ошибаются, иначе сказать не могу.

Джоел Спольски


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

Иногда в недостатках и процессах разработки архитектуры должно быть замешаны и другие люди. Чем раньше вы все это обсудите, тем лучше будет для всех.

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

5. SOLID



Это наиболее известный принцип разработки ПО. Solid это аббревиатура от:

S) Single-responsibility principle /Принцип единственной ответственности

Его важность невозможно переоценить. Каждый объект, класс и метод должны отвечать только за что-то одно. Если ваш объект/класс/метод делает слишком много, вы получите спагетти-код. Вот пример:

const saveTodo = async () => {    try {        response = await saveTodoApi();         showSuccessPop('Success');         window.location.href = '/successPage';    } catch (error) {         showErrorPopup(`Error: ${error} `);    }}


Этот метод кажется безобидным, но на самом деле он делает слишком много:

  1. Сохраняет объект
  2. Обрабатывает уведомление в UI
  3. Выполняет навигацию


Еще один побочный эффект такого кода проблемы с тестированием. Запутанный функционал тестировать сложно.

O) Openclosed principle / Принцип открытости-закрытости

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

Хороший способ решения этой проблемы использование наследования. В JavaScript эта проблема решается с помощью композиции.

Простое правило: если вы изменяете сущность, чтобы сделать ее расширяемой, вы впервые нарушили этот принцип.

L) Liskov substitution principle / Принцип подстановки Лисков

Этот принцип гласит, что объекты старших классов должны быть заменимы объектами подклассов, и приложение при такой замене должно работать так, как ожидается.

I) Interface segregation principle / Принцип разделения интерфейсов

Этот принцип был сформулирован Робертом Мартином, когда он консультировал Xerox, и он очевиден.

Объекты не должны зависеть от интерфейсов, которые они не используют


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

Убедитесь, что вы не заставляете объекты реализовывать методы, которые им никогда не понадобятся. Вот пример:

interface Animal {  eat: () => void;  walk: () => void;  fly: () => void;  swim: () => void;}


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

D) Dependency inversion principle / Принцип инверсии зависимостей

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

Заботиться нужно не о том, как что-то устроено, а о том, как оно работает. Простой пример использование дат в JavaScript. Вы можете написать для них свой слой абстракции. Тогда если у вас сменится источник получения дат, вам нужно будет внести изменения в одном месте, а не в тысяче.

Иногда добавление этого уровня абстракции требует усилий, но в конечном итоге они окупаются.

В качестве примера взгляните на date-io, в этой библиотеке создан тот уровень абстракции, который позволяет вам использовать её с разными источниками дат.

6. Avoid Premature Optimization

Избегайте преждевременной оптимизации

Эта практика побуждает разработчиков оптимизировать код до того, как необходимость этой оптимизации будет доказана. Думаю, что если вы следуете KISS или YAGNI, вы не попадетесь на этот крючок.

Поймите правильно, предвидеть, что произойдет что-то плохое это хорошо. Но прежде чем вы погрузитесь в детали реализации, убедитесь, что эти оптимизации действительно полезны.

Очень простой пример масштабирование. Вы не станете покупать 40 серверов из предположения, что ваше новое приложение станет очень популярным. Вы будете добавлять серверы по мере необходимости.

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

Многие считают преждевременную оптимизацию корнем всех зол.

7. Бритва Оккама



Бритва Оккама (иногда лезвие Оккама) методологический принцип, в кратком виде гласящий: Не следует множить сущее без необходимости[1] (либо Не следует привлекать новые сущности без крайней на то необходимости).

Википедия


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

Заключение


Эти принципы не очень сложны. На самом деле, именно простота делает их красивыми. Если вы сбиты с толку, не пытайтесь применять все их сразу. Просто постарайтесь работать осознанно и пробуйте постепенно включать эти принципы в свой рабочий процесс.

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

Если вы применяете большую часть принципов интуитивно, стоит задумываться и осознавать почему вы делаете что-то определенным образом.

Всего доброго.




image

Вакансии
НПП ИТЭЛМА всегда рада молодым специалистам, выпускникам автомобильных, технических вузов, а также физико-математических факультетов любых других высших учебных заведений.

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

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

Если вам интересно попробовать свои силы в решении тех задач, которые у нас есть, пишите в личку.



О компании ИТЭЛМА
Мы большая компания-разработчик automotive компонентов. В компании трудится около 2500 сотрудников, в том числе 650 инженеров.

Мы, пожалуй, самый сильный в России центр компетенций по разработке автомобильной электроники. Сейчас активно растем и открыли много вакансий (порядка 30, в том числе в регионах), таких как инженер-программист, инженер-конструктор, ведущий инженер-разработчик (DSP-программист) и др.

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

Подробнее..

0x7E5 Рассуждения о главном

19.03.2021 18:12:20 | Автор: admin

О себе

Приветствую всех. Меня зовут Вячеслав, работаю в IT 11 лет в направлении Android. Трогал и гладил динозавров в лице Android 1.5 и 1.6, прошел все этапы становления MVP MVVM Retrofit и многих других библиотек. Смотрел на свой старый код как на кучу г... много раз и все еще продолжаю изучать новое и развиваться. Мне удалось выучить не один десяток, не побоюсь этого слова, сильных ребят, с хорошим потенциалом и головой на плечах, в процессе обучения были сформированы правила и рекомендации, которыми я и хочу поделиться.

О статье

В последнее время сталкиваюсь с множество проектов разной сложности и вижу закономерную проблему. Начинающие программисты не видят ценности таких понятий как Clean Code, KISS и SOLID. Можно согласиться с тем что Clean Code - это далеко не для начинающих, однако считаю что в общих чертах, знание данного подход необходимо. Программисты среднего уровня - не в полной мере применяют данные подходы. Опытные программисты зачастую слишком сильно углубляются в детали и забывают о самом важном. Для начинающих: эта статья поможет собрать для себя правила, которым стоит уделить внимания.

Для опытных: пересмотреть свои взгляды или углубиться в детали современных подходов к написанию кода.

Для профессионалов: взглянуть на современные подходы под другим углом (надеюсь). Иногда полезно сделать шаг назад и убедиться что ты идешь верным путем.

Я не стану вдаваться во все аспекты разработки, больше времени будет уделено самим идеям и правилам, которым стоит уделить внимание во время разработки. Затрону некоторые современные библиотеки и решения в области реактивного программирования. Выскажу мнение в отношении архитектур и Clean Code.

Подходы

Clean Code

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

Начнем пожалуй с наиболее часто упоминаемого Clean Code. Желающие могут изучить данный материал за авторством Роберта Мартина, вдаваться же в детали не буду. Однако хочу вынести наиболее важный момент. Чистый код - подразумевает написание кода, который легко читаться и также легко дорабатываться (написать же при этом такой код зачастую довольно сложно). Во время обучения и работы - я всегда думаю о названиях функций и переменных, о целях классов и их назначениях. Было введено одно довольно интересное правило: Правило двух прочтений. Суть правила - если внимательно прочитав код 2 раза, кто-то не понял назначения либо реализации кода - это плохой код. Поставьте себя на место нового разработчика, или себя-же но через несколько лет. В любой ситуации код должен легко читаться. Код не должен быть замудренным однострочным решением, но и раздувать сортировку пузырьком на 100 строк тоже не стоит. Для особо сложных элементов всегда есть комментарии. Почему 2 раза? - первый раз мы вникаем в структуру, второй - в логику, обе пункта должны быть прозрачными для читающего. Как же добиться такого кода.. Начинающие программисты редко уделяют внимание довольно простой вещи - именованию, ведь оно отвечает за половину от читаемости кода. Всем понятно что делает функция transformDateToString и мало кто определяет назначение функции transDTS. Не все понимают что больше кода - не значит хуже, и меньше кода - не всегда хорошо. Никогда не измеряйте качество кода его количеством, кода должно быть достаточно для решения задачи и сохранения читаемости. Именно такие мелочи зачастую становятся преградами в понимании кода. Не стоит бояться длинных имен, не стоит недооценивать важность комментариев. Не забывайте: если это очевидно сейчас - это не значит что оно останется очевидным позже.

KISS

Таким образом мы плавно переходим к KISS (keep it simple, stupid). Как бы весело и немногозначно звучал этот принцип, я рекомендую ставить его на одно из первых мест при разработке ПО. Сделайте свой код настолько простым - насколько это возможно, это упростит жизнь, вам, вашим коллегам а может и следующему программисту на проекте. И вот тут я хочу отметить частую ошибку программистов среднего и старшего звена. В попытках следования таким направлениям как SOLID, многие забывают, что код, хоть с ним и работает машина, пишут все-же люди, и в первую очередь код должен быть читаемым. Не стоит излишне усложнять код.

    interface Factory<out T> {       fun create(): T    }    typealias PrinterFun = (String) -> Unit    interface PrinterFactory : Factory<PrinterFun>    interface MessageFactory : Factory<String>    interface MessagePrinter {       fun print(pf: PrinterFactory, mf: MessageFactory)    }    class PrinterFactoryImpl : PrinterFactory {       override fun create(): PrinterFun = ::print    }    class MessageFactoryImpl : MessageFactory {   companion object {       const val DEFAULT_MESSAGE = "Hello World"   }   override fun create(): String = DEFAULT_MESSAGE   class MessagePrinterImpl : MessagePrinter {       override fun print(pf: PrinterFactory, mf: MessageFactory) {           pf.create().invoke(mf.create())       }   }   class ImplProvider {       private val impls = HashMap<KClass<out Any>, Any>()       fun <T : Any> setImpl(clazz: KClass<T>, t: T) {           impls[clazz] = t       }       fun <T : Any> getImpl(clazz: KClass<T>): T {           return (impls[clazz] as? T) ?: throw Exception("No impl")       }   }   fun main(args: Array<String>) {       val implProvider = ImplProvider()       implProvider.setImpl(PrinterFactory::class, PrinterFactoryImpl())       implProvider.setImpl(MessageFactory::class, MessageFactoryImpl())       implProvider.setImpl(MessagePrinter::class, MessagePrinterImpl())       implProvider.getImpl(MessagePrinter::class)               .print(implProvider.getImpl(PrinterFactory::class),                       implProvider.getImpl(MessageFactory::class))   }

Много ли найдется желающих дорабатывать ТАКОЙ Hello world? Чем менее сложный код - тем легче его дорабатывать.

    class TimeFormatter {       private val timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())       fun formatTime() = timeFormat.format(Date())    }

И вот тут мы сталкиваемся с излишней простотой. Слишком простой код может быть сложно протестировать (UNIT тестами) из за отсутствие возможности подменять зависимости либо сильной связности кода. В примере выше имеет смысл добавить время, как параметр функции конвертации и для упрощения использования выставить параметру значение по умолчанию. Всегда старайтесь найти ту самую золотую середину.

SOLID

Вот мы и дошли до бича современной разработки: SOLID! В понятие вложено довольно большой объем знаний и понятий, но степень важности некоторых очень сильно недооценивают, а способы решения иных - слишком сильно возводят в абсолют. Конкретно данному набору принципов я бы хотел уделить особое внимание. Для начинающих этот набор выглядит как монстр и становится стеной непонимания, для средних - опорой, для профессионалов - постулатом, истинна же не в том как этим орудовать, а скорее в понимании для чего это нужно. Чтобы лучше что-то понять, нужно увидеть границы и исключения. Если же всё время показывать как надо, мы никогда не поймем а как НЕ надо, так что дальше мы разберем каждый пункт с примерами хорошего и плохого использования.

S - single responsibility

[WIKI] Принцип единственной ответственности (single responsibility principle). Для каждого класса должно быть определено единственное назначение. Все ресурсы, необходимые для его осуществления, должны быть инкапсулированы в этот класс и подчинены только этой задаче.

Есть и иное трактование: Модуль должен иметь одну и только одну причину для изменения.

Принцип разделяй и властвуй, в целом кажется довольно простым - пиши классы под определенные задачи, полезно и практично, однако зачастую можно столкнуться с паранойей. В своей практике встречал ситуацию когда в пакете утилит было около 20 классов с 1-2 методами (TextEditUtils, TextTransformUtils, TextConcatUtils и тд) - почему бы не объединить в TextUtils так и осталось загадкой. Не возводите этот принцип в абсолют, у всего есть границы, даже у безумия. Но и не стоит забывать что GOD-CLASS тоже плохо. Хоть и решение таких вопросов остается на совести разработчика, я не могу дать точных метрик и ограничений, так как каждый случай уникален. Ориентируйтесь на общий объем и связность. Если же взглянуть на второй вариант трактовки - возможно станет чуть более понятней. Проектируйте ваш код таким образом, чтобы причиной его изменить - могла быть только одна определенная задача. На примере выше, класс утилита для работы с текстом может иметь только одну логическую причину измениться - модификация взаимодействия со строками (добавление новой утилиты для удаления цифр в строке, удаление неиспользуемого метода и иные задачи относящиеся к манипуляциям текстом).

O - openclosed

[WIKI] Принцип открытости/закрытости. Программные сущности должны быть открыты для расширения, но закрыты для модификации.

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

Довольно неочевидный пункт для большинства. За начинающими программистами был замечен довольно интересный вопрос а зачем закрывать доступ?. И если подумать - а действительно, зачем? Если оставить все открытым и дозволенным, мы получим систему - в которой будет доступ ко всем компонентам без проблем, делай что хочешь. В такой ситуации стоит привести контрпример из практики преподавания. Я попросил своих студентов сделать довольно простой компонент - задачей стояло в зависимости от данных - отображать текст с картинкой, либо кнопку. Типовым решение стал вот такой код:

open class UiComponent() {   var mode : Int = 0   fun showTextAndImage(text:String, image: Image){       mode = 0       ...   }   fun showButton(text:String, action: Runnable){       mode = 1       ...   }   ...}

Фокусы же начались после того, как был написан довольно просто класс-наследник:

class MyUiComponent(): UiComponent(){   fun doMagic(){mode = 3}}

Вызов одной функции полность ломал поведение оригинала а студенты как один начали утверждать - флаг так менять нельзя, на логичный вопрос Почему? Флаг же открыт для изменения, почему я не могу его менять? так и не был дан полноценный ответ. Вот мы и пришли к выводу, не всё и не всегда должно быть открыто к модификации, иногда часть данных, участвующих в промежуточных расчетах или состояниях, могут меняться только в определенной части кода и по определенным правилам, и должны быть скрыты от внешнего взаимодействия и модификации. В данном примере стоило сделать переменную mode закрытой, а функции - переопределяемыми. Таким образом, можно было бы расширить функционал (например добавить форматирование текста перед отображением), но не модифицировать.

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

L - Liskov substitution

[WIKI] Принцип подстановки Лисков. Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Производный класс должен быть взаимозаменяем с родительским классом.

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

Мы писали довольно крупное приложение с возможностью скачивания файлов. Изначально это было одно место в коде и просто ссылка на файл. Не долго думая был реализован класс Downloader с функцией downloadFile(url). Позже появились новые типы файлов, вместе со ссылкой нужно было передавать параметры и хедеры для запроса, а для некоторых файлов нужно было еще дешифрование. По итогу был получен Downloader с кучей лишних функций на скачивание каждого типа файлов, а расширение или доработка становились адом. Решение (в упрощенном виде) было в вынесении абстракции Downloadable:

class DownloadManager() {   fun download(downloadable: Downloadable) {       val stream = downloadable.openStream()       val file = File(downloadable.getFileName())       //логика записи в файл   }}interface Downloadable {   fun openStream(): InputStream   fun getFileName(): String}class SimpleDownloadableFile(val name: String,                              val url: String) : Downloadable {   override fun openStream() = URL(url).openStream()   override fun getFileName() = name}class HeaderFile(val name: String,                  val url: String,                  val headers: Map<String, String>) : Downloadable {   override fun openStream(): InputStream {             /*формирование запроса и получении потока*/    }   override fun getFileName() = name}

Таким образом за счет данного принципа мы ушли от общих проблем скачивания (за счет подстановки объектов как имплементацию интерфейса) к частным задачам получения потока для каждого конкретного случая (по урл, по урл + хедеры и тд)

В противовес первому примеру - приведу обратное, ситуацию когда слишком сильное абстрагирование и выделение приводят лишь к непониманию и усложнению кода. К сожалению этим чаще страдают профессионалы. В попытке подготовиться ко всему - часто можно встретить цепочку наследования вида:

interface Somethinginterface SomethingSpecific : Somethinginterface WritableSomething : SomethingSpecific {   fun writeToFile()}interface GetableWritable<T> : WritableSomething {   fun obtain(): T}abstract class ObtainableFile(val name: String) : GetableWritable<File> {   override fun obtain() = File(name)   override fun writeToFile() = obtain().write(getStream())   abstract fun getStream(): InputStream}class UrlFile(url: String, name: String) : ObtainableFile(name) {   override fun getStream(): InputStream = URL(url).openStream()}

В момент разработки это хоть и может казаться необходимым, но все же стоит провести рефактор и избавиться от ненужных абстракций. Не стоит усложнять иерархию, это прямое нарушение принципа KISS. PS: я видел похожее в реальном проекте

I - interface segregation

[WIKI] Принцип разделения интерфейса. Много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения.

Один из самых сложных в понимании принципов. И самое сложное в данном принципе - это понять а кто есть клиент и осознать что зачастую мы сами и являемся клиентами. Вторая же сложность - это осознание значения слова интерфейс, которое зачастую воспринимается буквально interface / abstract class.

Смысл слова интерфейс в названии стоит воспринимать как точка доступа для более четкого осознания сути принципа. Точкой доступа может быть огромный класс на 1000 строк, но лишь с одной публичной функцией, а может быть обычный java interface, имплементацию которого мы скрыли.

Что же в отношении клиента - мы пишем классы и сами же ими пользуемся, а значит мы и клиент и производитель в одном лице. Мы производим части приложения (например класс Utils) и сами же потребляем и используем этот код. Сложность же в разграничении этих понятий. Нужно четко разделять код и выделять то что будет для клиента, при таком подходе будет получатся раделенный читабельный и структурированный код.

На самом же деле принцип довольно легко выводится из предыдущих принципов. Предоставляй интерфейс отдельной функциональности а не всех, вытекает из принципа S (Single responsibility). Open-close же говорит о том что не стоит давать доступ ко всему и стоит либо верно организовывать доступность методов и параметров либо выделить абстракцию. Liskov substitution же обязует такую абстракцию быть функциональной и расширяемой.

D - dependency inversion

[WIKI] Принцип инверсии зависимостей. Зависимость на Абстракциях. Нет зависимости на что-то конкретное.

Каждый раз, вспоминая этот принцип, я чувствую боль. Самый недооцененный и в то же время заезженный принцип. Для правильного понимания и использования данного принципа необходимо максимально четкое понимания причины его существования. Причин же в целом можно выделить много, но я остановлюсь на двух. Первая: следую принципу single responsibility, большая часть логики разбита по классам и нам необходимо объединить логику работы разных классов в одном (допустим класс для работы с базой данных и класс для работы с сервером, должны быть в классе для работы с данными, например, запросить данные с сервера и положить в базу). Вторая: тестируемость. Вопрос тестирования стоит рассматривать отдельно однако для полноценного тестирования нам необходимо заменять части логики, в данном случае используя принцип Liskov substitution мы можем подменить, к примеру, реализацию работы с сервером на ее виртуальный аналог с фиксированными результатами на определенные запросы.

Рассмотрим простой пример: нам необходимо получить с сервера данные и сохранить их в файл. Следуя принципам выше у нас получиться примерно такой код:

open class ServerManager {   open fun getData(): String = "запрос на сервер"}open class CacheManager {   open fun saveData(data: String) {/*сохранение в файл/базу данных */}}class DataManager{   fun getDataAndCache(){       val data = ServerManager().getData()       CacheManager().saveData(data)   }}

Недостатком же данного решения будет невозможность тестирования, так как мы не сможем заменить/подменить данные и сильная связанность, возникающая в результате создания других классов в теле метода.

Самым древним и простым способом реализации данного принципа - является способ передачи зависимостей через конструктор. Модифицируем DataManager из примера выше:

class DataManager(private val serverManager: ServerManager,                  private val cacheManager: CacheManager) {   fun getDataAndCache() {       val data = serverManager.getData()       cacheManager.saveData(data)   }}

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

Согласно Clean Architecture стоило бы выделить интерфейсы для каждого из классов менеджеров, однако это усложнило бы последующую разработку. Приведу пример идеального решения для ознакомления:

interface ServerManager {   fun getData(): String}open class ServerManagerImpl : ServerManager {   override fun getData(): String = "запрос на сервер"}interface CacheManager {   fun saveData(data: String)}open class CacheManagerImpl : CacheManager {   override fun saveData(data: String) {       /*сохранение в файл/базу данных */   }}interface DataManager {   fun getDataAndCache()}class DataManagerImpl(       private val serverManager: ServerManager,       private val cacheManager: CacheManager,) : DataManager {   override fun getDataAndCache() {       val data = serverManager.getData()       cacheManager.saveData(data)   }}fun foo(){   val dataManager: DataManager = DataManagerImpl(           ServerManagerImpl(),           CacheManagerImpl()   )   dataManager.getDataAndCache()}

Хоть это и самый простой подход (внедрение зависимостей через параметры конструктора) , он может иметь ряд недостатков (зависимость от большого числа классов).

Реализаций данного принципа довольно много (Dagger, Koin, ServiceLocator и тд), однако не стоит и перебарщивать. Зачастую можно заметить, как непонимание первопричин, приводят к появлению внедрения странных зависимостей:

interface TextProvider {   fun getText(): String}class SimpleTextProvider(private val text: String) : TextProvider {   override fun getText(): String = text}class Printer(private val textProvider: TextProvider) {   fun printText() {       println(textProvider.getText())   }}fun main() {   Printer(SimpleTextProvider("text")).printText()}

В данном примере вместо простой передачи текста был реализован класс, предоставляющий текст, далее согласно принципам SOLID выделен интерфейс, и проведен процесс Dependency injection. Однако очевидно что в данном случае мы получаем излишнюю функциональность и вырожденность кода. Гораздо проще передать текст для печати напрямую. Это и есть пример внедрения зависимостей ради внедрения, как можно заметить - излишнее стремление к совершенству лишь усложняет код и делает его трудно расширяемым и абсолютно противоречивым принципу KISS.

Самой большой проблемой данного подхода является определения звисимости, непонимание причин внедрения и целей приводит к тому, что программисты начинают внедрять всё. Нужно четко понимать цель внедрения - ослабления связанности и повышение тестируемости и не делать внедрение ради внедрения. Те же кто свято верят в постулат внедря всё и везде лишь делаю код абсолютно несвязанным и нечитаемым, усложняя работу себе и другим, забывая что полное отсутствие связности гораздо хуже слабой связности. Нет необходимости внедрять связанные компоненты (к примеру для Андроида - нет необходимости во внедрении Adapter-а, если сам адаптер не нуждается в зависимостях, просто используем конструктор и не мудрим).

О Важности архитектур

Начну пожалуй с того, что важности архитектуре приложения, к сожалению, не придают достаточного внимания. Понимание же смысла архитектуры оказывается важным пунктом в написания стабильного и качественного кода. На практике часто встречаются люди, решившие что архитектура это всего лишь набор правил или классов которые нужно реализовать. Хоть это и не далеко от истины - однако понимание назначения классов играет очень важную роль. Само же понятие и потребность в архитектуре - напрямую вытекают из рассмотренных выше подходов. Разделив наш код на классы для выполнения поставленных задач, необходимо правильно объединить и организовать данный код, сами же классы должны быть реализованы для выполнения достижения строго определенных целей - это по сути и есть архитектура: организация и целенаправленность кода. Известные архитектуры (MVP, MVVM и тд) это лишь набор правил, устоявшихся и сформулированных правил (сделать класс-модель, сделать класс-перзентер ). Важно понимать что архитектура позволяет значительно упростить и структурировать подход к разработке, выработать стратегию и правила. Известные архитектуры позволяют членам команды с большей эффективность работать над кодом, зная его структуру. Выбор же самой архитектуры должен осуществляться на основе поставленных задач.

Есть очень замечательная книга Clean architecture. И я ее торжественно ненавижу. Нет, не потому что она плохая или учит чему-то неправильному. К сожалению очень часто встречается Clean architecture головного мозга, чтение данной книги будет полезно для продвинутых программистов, для начинающих же это может стать постулатом и по итогу превратить в монстров, которые пишут внедрение зависимостей из примеров выше. Идеальную архитектуру написать можно - но работа с такой архитектурой будет занимать огромное количество времени. Тут стоит снова вернуться к примеру Hello World выше, аритектурненько ведь?

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

О том как думать

Меня часто спрашивают А как ты решаешь сложные задачи?, трудно ответить простыми словами. Важен подход, важен опыт, но алгоритм до боли известен: разбей сложную задачу на простые. На практике же, всегда нужно сводить сложные задачи к простым и понятным, искать наиболее простые решения. Боязнь ошибиться не должна останавливать от попыток. Даже самые сложные задачи можно свести к простым. Возьмем к примеру распознавание лиц, казалось бы довольно сложной задачей, а если подумать? Что есть лицо - 2 глаза, нос рот.. задачу найти лицо уже можно свести к задаче поиска частей лица ведь распознать нос гораздо проще чем лицо целиком. Как найти нос - задать шаблон и сравнивать. Как задать шаблон? Сделать фото носов, уменьшить, обесцветить и получить несколько шаблонных изображений. Таким образом даже самые сложные задачи всегда сводятся к более простым.

Не пытайтесь решить всё и сразу. Поэтапная разработка позволяет увидеть потенциал и ошибки на ранних стадиях разработки.

Во время обучения - важным фактором является понимание исключений и ограничений. Всегда нужно знать почему так НЕ надо делать, почему так плохо. Знания того как делать не надо становятся опорой во время разработки. Знания только лишь как надо - ограничениями. Всегда: если есть решение проблемы - необходимо понимать суть проблемы, с каждой решенной проблемой ваш багаж знаний будет расти, типовые проблемы станут мелочами и ваш опыт будет становиться ценнее.

Отдых - очень важный фактор в нашей работе. Порой во премя отдыха (рекомендую душ) приходят самый лучшие решения нашим задачам. Иногда нужно просто выгрузить всё, иногда нужен свежий взгляд. Не забывайте про уточку (метод уточки/утёнка).

Заключение

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

Подробнее..

CDD Cli Driven Development

03.04.2021 10:09:09 | Автор: admin

Все-таки самоизоляция не проходит бесследно. Сидишь себе дома, а в голову разные мысли приходят. Как, чем осчастливить человечество? И вот оно: CDD! (И еще PDD / SOLID / KISS / YAGNI / TDD / Bootstraping...)

1. CDD - Cli Driven Development - Новый подход

Немного истории

Как-то поручили мне сделать Cli в одном нашем embedded устройстве. Разумеется, C/C++ (пусть будет C++, раз ресурсов хватает). Конечно есть много Cli-фреймворков.

Но я сделал свой вариант.

Для Linux можно использовать <termios.h> и получать коды символов после установки свойств терминала:

signal(SIGINT, SIGINT_Handler); // Ctrl+Csignal(SIGTSTP, SIGTSTP_Handler); // Ctrl+Zint res_tcgetattr = tcgetattr(STDIN_FILENO, &terminal_state_prev);terminal_state_new = terminal_state_prev;terminal_state_new.c_lflag &= ~(ICANON | ECHO);int res_tcsetattr = tcsetattr(STDIN_FILENO, TCSANOW, &terminal_state_new);

Для Windows можно использовать <conio.h>.

Добавляем немного классов, делаем список команд, и добавляем команды по типу:

{ Cli_Command_Abstract_t *cmd = new Cli_Command_Abstract_t(Cli_Command_ID_help); cmd->Add(help_keyword); cmd->Help_Set("show this help, \"help full\" - show all available commands"); command_tree->Add(cmd);}

И все-бы ничего, пока команд 10-20. Ну пусть еще help / quit / debug cli (типа очень нужная команда - об этом позже). Интересно, что основной функционал уложился в 20 команд, а вот разные обвязки Управление SNMP / Syslog / NTP / Users / FTP / SSH / VLAN и у нас - 250 команд. Ух ты! Начинаются проблемы с монолитным приложением, и очень хочется разбить все на модули, желательно попроще и поменьше. И вот отсюда и начинается CDD - Cli Driven Development.

1.1 Использование Cli в различных типах приложений

Вообще, Cli, не смотря на GUI, используется во многих типах приложений: САПР, игры, базы данных, среды выполнения (Erlang, Lua и др.), IDE. Можно утверждать, что включение консоли могло бы сделать многие приложения более удобными (например, можно представить Paint с командной строкой: количество команд невелико, VBA будет лишним, но одна лишь возможность выполнения скриптов могла бы значительно изменить работу с программой).

1.2 Введение в CDD

Cli-интерфейс жив и развивается. Cisco-like - это вполне вполне рабочий термин.

Что же может современный Cli? - Довольно много:

  • развитую систему команд с выводом подробной информации, в том числе об аргументах команды;

  • группировку команд ("уровни");

  • задание группы объектов для управления ("параметры");

  • логгирование;

  • исполнение скриптов;

  • типизированный ввод данных с валидацией;

Я придумал еще одну функцию: debug cli - проверка команд (CMD_ID / CMD_Item / CMD_Handler)

  • может показать число ID ("задуманные команды"), Realized- и NotRealized-команды для каждого модуля; (В идеале счетчики ID, Realized должны быть равны, но если NotRealized не равен 0, то это еще один стимул для разработчика: ну осталось всего-то 30...20...5...2 нереализованных команд - неужели оставим так? может лучше доделать? - и это работает!)

1.3 Основные идеи CDD

Можно сформулировать основные идеи CDD:

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

  2. Модульное построение: любой модуль можно убрать или заменить с предсказуемыми изменениями в функционале.

  3. Модули связываются только на самом верхнем уровне: все связи должны легко прослеживаться (фактически мы пользуемся тем, что приложений с полной связностью не существует / не может быть / мы должны избегать).

1.4 mCli - Реализация CDD

CDD использовано при построении mCli - Cli-фреймворка модульного типа (github.com/MikeGM2017/mCli). В текущем состоянии имеются события, типы и модули.

1.4.1 События mCli

В простейшем виде для ввода с клавиатуры нужно определение кода нажатой клавиши и (отдельно) определение нажатия Enter (ввод команды) и Ctrl+C (прерывание команды). В полном наборе необходимо определение нажатия Enter (ввод команды), Ctrl+C (прерывание команды), Up/Down (просмотр истории команд), Left/Right/Home/End (перемещение по строке ввода), Back/Delete (изменение строки ввода).

1.4.2 Типы mCli

mCli предполагает использование типов при вводе данных. В текущей реализации имеются следующие типы:

  • Word / Word_List / Word_Range (ключевые слова, List - можно ввести несколько ключевых слов через запятую, Range - выбор одного ключевого слова из нескольких вариантов)

  • Int / Int_List / Int_Range

  • Str

  • IP4 / IP6

  • MAC

  • Date / Time / DateTime

  • EQU_Range ( == != > < >= <= - для использования в скриптах, условное выполнение)

  • Rem (комментарий - для использования в скриптах)

1.4.3 Модули mCli

Модули mCli можно разделить на базовые, платформо-зависимые и кастомные.

Базовые модули:

  • Base_Quit (выход из приложения)

  • Base_Help (вывод информации по командам и их аргументам)

  • Base_Modules (вывод информации по задействованным модулям)

  • Base_History (история команд)

  • Base_Script (выполнение скриптов)

  • Base_Rem (комментарий, для использования в скриптах)

  • Base_Wait (пауза, для использования в скриптах)

  • Base_Log (управление логом)

  • Base_Debug (проверка списка команд, определение нереализованных команд)

  • Check (условное выполнение, для использования в скриптах)

Платформо-зависимые модули

Вывод:

  • Output_printf (Linux/Window)

  • Output_cout (Linux/Window)

  • Output_ncurses (Linux)

  • Output_pdcurses (Linux/Window)

Ввод:

  • Input_termios (Linux)

  • Input_conio (Window)

  • Input_ncurses (Linux)

  • Input_pdcurses (Linux/Window)

Кастомные модули:

  • ConfigureTerminal (демо: тестирование переменных)

  • SecureTerminal (демо: вход в модуль по паролю)

  • TestTerminal (демо: тестирование типов)

1.5 Объединение модулей в mCli

Связывание модулей происходит на самом верхнем уровне, например в функции main():

Cli_Modules Modules;// Modules Add - BeginModules.Add(new Cli_Module_Base_Rem(Str_Rem_DEF, Cli_Output));bool Cmd_Quit = false;Modules.Add(new Cli_Module_Base_Quit(Cmd_Quit));Str_Filter str_filter('?', '*');Modules.Add(new Cli_Module_Base_Help(User_Privilege, Modules, str_filter, Cli_Output));Modules.Add(new Cli_Module_Base_Modules(User_Privilege, Modules, str_filter, Cli_Output));Cli_History History;Modules.Add(new Cli_Module_Base_History(History, Cli_Output));Modules.Add(new Cli_Module_Base_Log(Cli_Input));bool Cmd_Script_Stop = false;int Script_Buf_Size = 1024;Modules.Add(new Cli_Module_Base_Script(History, Cli_Output,            Str_Rem_DEF, Cmd_Script_Stop, Cmd_Quit, Script_Buf_Size,            CMD_Processor));bool Log_Wait_Enable = true;bool Cmd_Wait_Stop = false;Modules.Add(new Cli_Module_Base_Wait(Log_Wait_Enable, Cmd_Wait_Stop, Cli_Input, Cli_Output));Modules.Add(new Cli_Module_Test_Tab_Min_Max());Modules.Add(new Cli_Module_Test_Terminal(Cli_Input, Cli_Output));Modules.Add(new Cli_Module_Base_Debug(User_Privilege, Modules, Levels, CMD_Processor, Cli_Output));Modules.Add(new Cli_Module_Check(Modules, Values_Map, str_filter, Cli_Output, Cmd_Script_Stop));// Modules Add - End

1.6 CDD и SOLID

SOLID в CDD достаточно легко обнаружить на уровне подключения и объединения модулей. Какие-то модули практически всегда используются, например Cli_Output нужен в большинстве модулей. Другие - гораздо реже (например, Cli_Input нужен только в модулях, в которых команда требует подтверждения).

Таким образом, SOLID в CDD - это:

  • S - каждый модуль отвечает за свой круг задач

  • O - здесь есть проблема: в каждом модуле есть enum Local_CmdID, и получается, что при наследовании список Local_CmdID не так просто расширить? Но в новом модуле мы можем завести новый enum Local_CmdID или (лучше) можно ввести новый enum Local_CmdID только для новых команд, стартующий с последнего элемента предыдущего enum (для этого можно использовать CMD_ID_LAST)

  • L - модуль может быть заменен на другой, с доработанной реализацией

  • I - при замене модуля может возникнуть ситуация, что потребуется больше (или меньше) связанных модулей; при создании экземпляра модуля это легко учесть (через конструктор или статический инициализатор)

  • D - модули связываются на верхнем уровне

1.7 CDD и KISS

В целом, набор команд любого модуля стремится к расширению: более удобный формат команд, новые флаги, новые возможности. Но сам список команд всегда доступен, и, если он сильно разрастается то просто делим модуль на 2 или более модуля!

На уровне модуля команды могут различаться флагами или дополнительными параметрами. Но реализация у них может быть одна. Например, "help" и "help full" реализуются одним методом, в качестве параметра принимающий строку фильтра - "*". Так что KISS сохраняется в таком смысле:

  • команда выполняется методом, имеющим несколько флагов (да, из-за этого метод делается чуть сложнее, зато несколько команд Cli могут выполняться однотипно).

1.8 CDD и DRY

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

1.9 CDD и YAGNI

Нужно убрать какой-то ненужный функционал? - Убираем ненужный модуль (или команды в модуле). За счет слабой связности модулей это несложно.

1.10 CDD и Bootstraping

В некоторых случаях (например, Embedded Baremetal) у нас есть только консоль. CDD может быть применено для разработки приложения "с нуля".

1.11 CDD и TDD

За счет наличия скриптов и модуля условного исполнения автоматизация тестирования сводится к следующему сценарию:

  • вручную вводится последовательность тестируемых команд;

  • история команд сохраняется в файле скрипта;

  • при необходимости скрипт редактируется / дополняется проверкой правильности выполнения;

  • вызов скрипта добавляется в общий скрипт тестирования или используется сам по себе;

1.12 CDD и GUI

А что GUI? GUI (да и Web - тоже) пусть посылает текстовые команды в Cli - эстетично, наглядно, надежно.

2. CDD и PDD

А вот еще и PDD!!!

2.1 PDD - Provocation Driven Development - еще один новый термин :)

Вообще, PDD - это то, что нас настигает постоянно. Допустим, есть путь, по которому мы идем к цели. Но на что нас провоцирует этот путь? Считаю, что мы должны осознавать это. Например, на что провоцируют языки программирования:

  • C провоцирует на нарушения доступа к памяти и на плохо контролируемые приведения типов;

  • C++ - на создание монолита (если за этим не следить, то имеем типовой пример: classMyCoolGame;myCoolGame.Run());

  • SQL, Lua - "все есть таблица";

  • Assembler - "стандартов нет";

  • Java - "щас понаделаем объектов";

  • JavaScript - "щас наподключаем библиотек, не самим же все делать"; и так далее - дополнительные примеры каждый, думаю, сможет придумать.

2.2 Что есть PDD для CDD?

В первую очередь - это тенденция на разбиение проекта на модули. Действительно:

  • Есть объект управления? - Выносим в модуль.

  • Есть повторяющийся код? - Выносим в модуль.

  • Новый функционал? - Добавляем новый модуль.

  • Новая архитектура? - Заменяем модули.

Описание команд - это текстовое описание функционала, фактически мы получаем DSL. Чтобы получить информацию о доступном функционале, достаточно ввести команду "help".

Предсказательный характер архитектуры:

  • пусть в расчетах на каждую Cli-команду отводим 1 (один) человеко-день. Да, можно за 1 день ввести 10-20 простых Cli-команд (да, простые или однотипные команды реализуются быстро), но не нужно обманываться: будет (обязательно будет!) функция, которая потребует 10 дней на реализацию и тестирование. Поэтому проект средней сложности на 200-300 Cli-команд займет 200-300 человеко-дней (хотя, это скорее оценка "сверху", реально проект может быть закончен раньше).

Скрипты с возможностью условного исполнения означают встроенную возможность тестирования, что (в теории) уменьшает вероятность регресса.

Расширяемость: новый модуль может, конечно, добавлять новые команды в глобальную видимость, но предпочтительный путь - группировка команд на уровне модуля, тогда в глобальной видимости появится всего лишь одна команда - переход на "уровень" модуля.

В Cli достаточно легко задавать обработку группы объектов. Можно, например:

  • ввести список объектов в команду;

  • ввести фильтр по именам объектов в команду;

  • ввести список объектов как параметр;

  • или даже ввести модуль Selection для управления группировкой объектов. Таким образом, можно использовать различные варианты групповой обработки.

Приложение достаточно легко поддерживать в постоянно рабочем состоянии: да, какой-то функционал еще не введен, зато все остальное - работает.

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

3. Встроенный язык скриптов

3.1 Модуль Check

Условное выполнение реализовано в модуле "Check".

Для условного выполнения команд, в принципе, достаточно всего двух команд: "check label " - установка метки "check if == goto " - условный переход (здесь сравнение может быть не только на равенство: == != > < >= <= - вот полный список, но при этом команду можно оставить одну и ту же, а операторы сравнения ввести в виде списка возможных значений)

Переменные в простейшем случае - глобальные, заносятся в map<string,string>, для чего в модуле предусмотрен виртуальный метод .To_Map().

Для работы с переменными введены команды условного и безусловного присвоения, объединения, вывода на экран. Для полноценного языка этого, возможно, мало, но для задач тестирования функционала - вполне приемлемо.

3.2 Модуль Check vs Lua

Да, вместо встроенных модулей скриптов и условного выполнения можно подключить Lua. Однако, вместо нескольких команд (в действительности модуль условного выполнения Check получается не такой уж маленький - более 30 команд, хотя и однотипных) подключение Lua означает большое увеличение размера исполняемого файла, а в некоторых случаях это может быть критичным. Но как вариант, Lua выглядит очень привлекательно.

3.3 Модуль Check vs Erlang

Было бы неплохо, но запросы Уж очень большие у Erlang требования к ресурсам. Возможно, на "жирных" устройствах, как вариант, можно и Erlang подключить.

4. CDD vs Erlang

Неплохая попытка, подход Erlang - довольно похож на CDD. Но задумаемся, в чем PDD для Erlang? - "Ошибаемся и еще раз ошибаемся, а система все равно работает". Это, конечно, сильно. Поэтому вопрос: "CDD или Erlang" безусловно стоит. Но CDD можно реализовать на многих языках программирования (C/C++, C#, Java, JavaScript). А у Erlang - очень специфичный подход. Может быть, не Erlang vs CDD, а Erlang + CDD ??? Кажется, надо попробовать...

5. CDD и дробление монолита

Примерный путь преобразования монолита в CDD-приложение:

  • создаем CDD-приложение из Base-модулей;

  • legacy-монолит добавляем в виде нового Cli-модуля на новом "уровне" с минимальными командами вида "version get" / "info get" - на первом этапе достаточно "установить контакт" с монолитом;

  • в новом модуле вводим команды, специфичные для него: "start" / "stop" / "configure" ;

  • скорее всего новые команды будут группироваться вокруг каких-то понятий / объектов / процедур и т.п. - это повод выделить такие группы в отдельные модули + объекты управления; при этом в основном монолите вводятся ссылки на выделенные объекты;

  • в результате должен получиться набор модулей, причем каждый модуль должен содержать не более 10-20 команд;

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

6. Итоги

CDD выполняет SOLID, KISS, DRY, YAGNI, Bootstraping, TDD.

CDD провоцирует на модульное построение.

CDD дает возможность выполнения скриптов и внутреннее тестирование.

CDD может быть основой большого количества типов приложений.

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

CDD может быть основой построения OS.

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

CDD дает возможность разделения работ:

  • постановщик задачи описывает новый модуль в виде набора команд;

  • исполнитель реализует команды;

  • тестировщик пишет скрипты для проверки нового функционала.

CDD поддерживает введение нового функционала, в том числе на разных уровнях:

  • новые модули;

  • новые команды в существующих модулях.

CDD обеспечивает безопасность при вводе команд:

  • команды парсятся, данные валидируются, сделать что-то вне Cli-команд невозможно (если, конечно, не вводить команды типа exec / system / eval).

CDD фактически дает документацию по функционалу приложения:

  • достаточно подать команду "help * verbose" - и описание команд и их аргументов уже есть.

Этого мало?

Тогда вот вам напоследок: CDD позволяет захватить мир. КМК

Да, и Linux стоит переписать по CDD. КМК

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru