Насколько велики базы не OO кода?

27

Я всегда вижу, что абстракция - очень полезная функция, предоставляемая ОО для управления базой кода. Но как управлять большими базами не-OO кода? Или они просто становятся " Большим Грязевым комом "?

Обновление:
казалось, все думают, что «абстракция» - это просто модульность или скрытие данных. Но IMHO, это также означает использование «абстрактных классов» или «интерфейсов», которые необходимы для внедрения зависимостей и, следовательно, для тестирования. Как не-OO кодовые базы управляют этим? Кроме того, помимо абстракции, инкапсуляция также очень помогает в управлении большими базами кода, поскольку она определяет и ограничивает отношения между данными и функциями.

С C очень возможно написать псевдо-OO-код. Я не знаю много о других не-ОО языках. Итак, это способ управлять большими базами кода C?

Gulshan
источник
6
Пожалуйста, опишите объект, не зависящий от языка. Что это такое, как оно модифицируется, что оно должно наследовать и что оно должно предоставлять? Ядро Linux полно распределенных структур с множеством помощников и указателей на функции, но это, вероятно, не удовлетворяет определению объектно-ориентированного большинства. Тем не менее, это один из лучших примеров очень хорошо поддерживаемой кодовой базы. Зачем? Потому что каждый сопровождающий подсистемы знает, что находится в их зоне ответственности.
Тим Пост
Не зависящим от языка, пожалуйста, опишите, как вы видите, как работают базы кода, и как OO имеет к этому отношение.
Дэвид Торнли
@Tim Post Меня интересует управление исходным кодом ядра Linux. Не могли бы вы описать систему подробнее? Возможно, как ответ с примером?
Гюльшан
7
В старые времена мы использовали отдельные ссылки для макетов и заглушек для модульного тестирования. Инъекция зависимостей - это всего лишь одна из нескольких техник. Условная компиляция это другое.
Макнейл
Я считаю, что называть большие управляемые базы кода (OO или иным образом) «управляемыми» довольно сложно. Было бы хорошо иметь лучшее определение центрального термина в вашем вопросе.
Тоттинг

Ответы:

43

Вы, кажется, думаете, что ООП является единственным средством достижения абстракции.

Хотя ООП, безусловно, очень хорошо это делает, это ни в коем случае не единственный способ. Большие проекты также можно поддерживать управляемыми с помощью бескомпромиссной модульности (достаточно взглянуть на Perl или Python, оба из которых в этом преуспели, как и функциональные языки, такие как ML и Haskell), а также с помощью таких механизмов, как шаблоны (в C ++).

Конрад Рудольф
источник
27
+1 Также можно написать «Большой шарик грязи», используя ООП, если вы не знаете, что делаете.
Ларри Коулман
А как насчет основ кода на C?
Гульшан
6
@Gulshan: многие большие базы кода на С являются ООП. Тот факт, что C не имеет классов, не означает, что ООП не может быть достигнуто с небольшим усилием. Кроме того, C обеспечивает хорошую модульность с использованием заголовков и идиомы PIMPL. Не так удобно и мощно, как модули на современных языках, но опять же достаточно хорошо.
Конрад Рудольф
9
C позволяет модуляризацию на уровне файлов. Интерфейс идет в .h файле, публично доступные функции в файле .c, и частные переменные и функции получить staticдоступ к Модификатор прилагается.
Дэвид Торнли
1
@Konrad: хотя я согласен с тем, что ООП - не единственный способ сделать это, я считаю, что OP, вероятно, имел в виду строго C, который не является ни функциональным, ни динамическим языком. Поэтому я сомневаюсь, что упоминание Perl и Haskell будет ему полезно. Я считаю, что ваш комментарий более актуален и полезен для ОП ( это не значит, что ООП не может быть достигнуто с небольшими усилиями ); Вы можете добавить его в качестве отдельного ответа с дополнительными подробностями, возможно, с использованием фрагмента кода или пары ссылок. Это, по крайней мере, выиграло бы мой голос, и вполне возможно, ОП. :)
Groo
11

Модули, (внешние / внутренние) функции, подпрограммы ...

Как сказал Конрад, ООП - не единственный способ управления большими базами кода. Фактически, до этого было написано довольно много программного обеспечения (до C ++ *).

ладья
источник
* И да, я знаю, что C ++ не единственный, поддерживающий ООП, но почему-то именно тогда этот подход начал принимать инерцию.
Ладья
8

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

Пол Натан
источник
6

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

Лучшими решениями являются повторяемая валидация, под которой я подразумеваю автоматический тест (например, модульное тестирование) и тестирование на людях, которое следует за запрещенными шагами (например, регрессионное тестирование) «вместо того, чтобы щелкать и смотреть, что ломается».

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

orangepips
источник
+1 за книгу Майкла Пера. Когда вы чувствуете себя подавленным из-за большой уродливой кодовой базы, перечитайте ее :)
Матье
5

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

Я обнаружил, что строгая работа с метаданными очень полезна. В ОО-языках отношения между битами кода определяются (до некоторой степени) структурой класса, достаточно стандартизированным способом, чтобы иметь такие вещи, как API отражения. На процедурных языках может быть полезно придумать их самостоятельно.

Я также обнаружил, что генерация кода гораздо более полезна в процедурном языке (по сравнению с объектно-ориентированным языком). Это гарантирует, что метаданные синхронизируются с кодом (поскольку он используется для его генерации) и дает вам что-то похожее на точки отсечения аспектно-ориентированного программирования - место, где вы можете вводить код, когда вам это нужно. Иногда это единственный способ сделать СУХОЕ программирование в такой среде, что я могу понять.

PSR
источник
3

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

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

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

оборота back2dos
источник
2

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

В небольших и средних проектах это иногда проще сделать с помощью пуристической ОО-реализации.

Билл
источник
2

Абстракция, абстрактные классы, внедрение зависимостей, инкапсуляция, интерфейсы и т. Д. Не являются единственным способом управления большими базами кода; это справедливый и объектно-ориентированный способ.

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

Модульность является ключом в неOO языках. В Си это достигается так же, как Дэвид Торнли только что упомянул в комментарии:

Интерфейс находится в файле .h, общедоступные функции в файле .c, а закрытые переменные и функции прикрепляются к модификатору статического доступа.

mouviciel
источник
1

Одним из способов управления кодом является его разложение на следующие типы кода в соответствии с архитектурой MVC (модель-представление-контроллер).

  • Обработчики ввода - этот код имеет дело с устройствами ввода, такими как мышь, клавиатура, сетевой порт или абстракции более высокого уровня, такие как системные события.
  • Обработчики вывода - этот код имеет дело с использованием данных для управления внешними устройствами, такими как мониторы, индикаторы, сетевые порты и т. Д.
  • Модели - этот код имеет дело с объявлением структуры ваших постоянных данных, правил проверки постоянных данных и сохранения постоянных данных на диск (или другое устройство постоянных данных).
  • Представления - этот код имеет дело с форматированием данных для удовлетворения требований различных методов просмотра, таких как веб-браузеры (HTML / CSS), GUI, командная строка, форматы данных протокола связи (например, JSON, XML, ASN.1 и т. Д.).
  • Алгоритмы - этот код повторяет преобразование входных данных в набор выходных данных как можно быстрее.
  • Контроллеры - этот код принимает входные данные через входные обработчики, анализирует входные данные с использованием алгоритмов, а затем преобразует данные с другими алгоритмами, при желании комбинируя входные данные с постоянными данными или просто преобразовывая входные данные, а затем дополнительно сохраняя преобразованные данные в постоянные с помощью модели. программное обеспечение и, необязательно, преобразование данных через программное обеспечение для просмотра для вывода на устройство вывода.

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

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

Один из способов ОО-программирования усложняет организацию кода, поскольку некоторые классы тесно связаны с постоянными структурами данных, а некоторые - нет. Если постоянные структуры данных тесно связаны с такими вещами, как каскадные отношения 1: N или отношения m: n, очень трудно определить границы классов, пока вы не закодируете значимую и значимую часть своей системы, прежде чем узнаете, что поняли ее правильно. , Любой класс, связанный с постоянными структурами данных, будет трудно развить при изменении схемы постоянных данных. Классы, которые обрабатывают алгоритмы, форматирование и синтаксический анализ, менее подвержены изменениям в схеме постоянных структур данных. Использование организации кода типа MVC лучше изолирует самые грязные изменения кода в коде модели.

Джей Годсе
источник
0

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

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

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

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

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

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

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

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

AndyHasIt
источник
0

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

Док Браун
источник
0

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

  1. свободная функция
  2. функция-член
  3. учебный класс
  4. объект
  5. интерфейс
  6. выражение
  7. вызов конструктора / создание объектов
  8. вызов функции
  9. тип параметра шаблона

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

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

оборота тп1
источник
-1

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

Джонатан Клайн IEEE
источник
-2

Emacs является хорошим примером этого:

Emacs Architecture

Компоненты Emacs

В тестах Emacs Lisp используются skip-unlessи let-bindиспользуются средства обнаружения и тестирования функций:

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

 (ert-deftest test-dbus ()
   "A test that checks D-BUS functionality."
   (skip-unless (featurep 'dbusbind))
   ...)

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

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

Как и SQLite. Вот это дизайн:

  1. sqlite3_open () → Открыть соединение с новой или существующей базой данных SQLite. Конструктор для sqlite3.

  2. sqlite3 → Объект подключения к базе данных. Создается sqlite3_open () и уничтожается sqlite3_close ().

  3. sqlite3_stmt → Подготовленный объект выписки. Создается с помощью sqlite3_prepare () и уничтожается с помощью sqlite3_finalize ().

  4. sqlite3_prepare () → Скомпилируйте текст SQL в байт-код, который будет выполнять запросы или обновлять базу данных. Конструктор для sqlite3_stmt.

  5. sqlite3_bind () → Сохранить данные приложения в параметры исходного SQL.

  6. sqlite3_step () → Переместить sqlite3_stmt к следующей строке результатов или к завершению.

  7. sqlite3_column () → Значения столбцов в текущей строке результатов для sqlite3_stmt.

  8. sqlite3_finalize () → Деструктор для sqlite3_stmt.

  9. sqlite3_exec () → Функция-обертка, которая выполняет sqlite3_prepare (), sqlite3_step (), sqlite3_column () и sqlite3_finalize () для строки из одного или нескольких операторов SQL.

  10. sqlite3_close () → Деструктор для sqlite3.

архитектура sqlite3

Компоненты Tokenizer, Parser и Code Generator используются для обработки операторов SQL и преобразования их в исполняемые программы на языке виртуальной машины или в байт-код. Грубо говоря, эти три верхних уровня реализуют sqlite3_prepare_v2 () . Байтовый код, сгенерированный тремя верхними слоями, является подготовленным оператором, Модуль виртуальной машины отвечает за выполнение байтового кода оператора SQL. Модуль B-Tree организует файл базы данных в несколько хранилищ ключей / значений с упорядоченными ключами и логарифмической производительностью. Модуль Pager отвечает за загрузку страниц файла базы данных в память, за реализацию и контроль транзакций, а также за создание и обслуживание файлов журнала, которые предотвращают повреждение базы данных после сбоя или сбоя питания. Интерфейс ОС - это тонкая абстракция, предоставляющая общий набор процедур для адаптации SQLite для работы в разных операционных системах. Грубо говоря, нижние четыре слоя реализуют sqlite3_step () .

Виртуальная таблица sqlite3

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

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

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

Интерфейс полнотекстового поиска
Пространственные индексы с использованием R-деревьев
Анализ содержимого диска файла базы данных SQLite (виртуальная таблица dbstat)
Чтение и / или запись содержимого файла значений, разделенных запятыми (CSV)
Доступ к файловой системе главного компьютера, как если бы это была таблица базы данных
Включение SQL манипулирования данными в статистических пакетах, таких как R

SQLite использует различные методы тестирования, в том числе:

Три независимо разработанных тестовых ремня безопасности
100% покрытие тестов ветвлений в развернутой конфигурации
Миллионы и миллионы тестовых случаев
Тесты нехватки памяти
Тесты ошибок ввода / вывода
Испытания на столкновение и потерю мощности
Нечеткие тесты
Граничные тесты
Отключенные оптимизационные тесты
Регрессионные тесты
Испорченные тесты базы данных
Широкое использование assert () и проверки во время выполнения
Анализ Valgrind
Неопределенные проверки поведения
перечни

Ссылки

Пол Свитт
источник