Программист
1,0
рейтинг
26 ноября 2013 в 00:35

Разработка → Ущербно-ориентированное программирование перевод

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

Ущербный — имеющий изъян, неполноценный. Вредный, недостаточный.

Наследование


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

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

Пример наследования в виде псевдокода:

function getCustName(custID)
{
    custRec = readFromDB("customer", custID);
    fullname = custRec[1] + ' ' + custRec[2];
    return fullname;
}

function getCustEmail(custID)
{
    custRec = readFromDB("customer", custID);
    fullname = custRec[1] + ' ' + custRec[2];
    /***************
    * 4/15/96 git : адрес email хранится
    * во втором поле для факса
    ***************/
    return custRec[17];
}

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

Полиморфизм типов — один из типов наследования. В нем при наследовании кода заменяются типы исходных переменных.

Модульность


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

  • Копирайт
  • Отказ от ответственности
  • От трех до пяти строк со звездочками
  • История изменений
  • Описание того, что модуль изначально должен был делать
  • Еще три — пяти строк со звездочками
  • Большой блок с пробелами и пустыми строками, окруженный звездочками или другими символами, в котором перечислены названия всех функций, имя или инициалы автора, а также дата написания
  • Код

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

Компоненты и библиотеки


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

Инкапсуляция


Суть инкапсуляции заключается в отделении данных от кода. Иногда этот подход также называется сокрытием данных, но данные на самом деле не скрываются — они размещаются в дополнительном слое кода. Например, когда в коде повсюду запросы к базе данных — это плохая практика. Ущербно-ориентированный подход предполагает написать обертки для этих функций, тем самым инкапсулировав базу данных. В методе getCustName, представленном выше, обращение к базе данных идет не напрямую, а через такую специальную обертку, считывающую одну запись. Оба этих метода (а также еще множество аналогичных им) «знают», где в записи о клиенте есть необходимые им данные. Способ считывания записи из базы инкапсулирован в каком-нибудь другом модуле.

В некоторых языках программирования поля можно помечать модификаторами private, public или protected. Это не имеет ничего общего с ущербно-ориентированным подходом. Автор модуля никак не может знать, какие внутренние переменные из его модуля потребуются для реализации требуемого функционала в будущем! Поэтому все поля следует делать публичными, а переменные — глобальными, и пусть внешний код сам решает, что трогать, а что нет.

Полиморфизм


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

Например, функции выше можно переписать в одну полиморфную функцию, используя наследование и полиморфизм:

function getCustData(custId, what)
{
    if (what == 'name') {
        custRec = readFromDB("customer", custId);
        fullname = custRec[1] + ' ' + custRec[2];
        return fullname;
    } else if (what == 'email') {
        custRec = readFromDB("customer", custId);
        fullname = custRec[1] + ' ' + custRec[2];
    /***************
    * 4/15/96 git : адрес email хранится
    * во втором поле для факса
    ***************/
        return custRec[17];
    }

    /* ... и так далее. */
}

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

"Является" против "содержит"


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

Виртуальные классы и функции


Виртуальный класс или функция — это код, который понадобится в программе в будущем, однако пока еще не написан. Как правило, это реализуется посредством базового класса, на котором будет основан финальный код.

function calcSalesTax(price, isTaxable, state)
{
    /****************************************
    *
    * TO DO:
    *
    * получать тариф для пользователя
    * из какой-нибудь таблицы, когда ее сделают
    *
    *
    ****************************************/

    /** 02/07/99 git -- пока возьмем такой тариф **/
    return price * (7.25 / 100.0);
}

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

Перегрузка


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

Документация


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

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

Забивание кода комментариями, объявляющими механизмы его работы, отвлекает внимание и замедляет компиляцию. Поэтому программисты, которые следуют «лучшим практикам» ущербно-ориентированного программирования хранят документацию в системе учета документооборота — чтобы программисты не могли ее случайно затереть.

Управление версиями


Это, конечно, не то чтобы практика программирования, но последователи ущербно-ориентированного подхода ее применяют. Хранение предыдущих версий и истории изменений под рукой важно, даже если над проектом работает один человек. Опытные ущербно-ориентированные программисты придерживаются следующих правил:

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

Заключение


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

От переводчика: хоть статья и 2007 года, но актуальности не потеряла. Спасибо vovochkin за ссылку в статье "Как разрабатывать неподдерживаемое ПО".
Перевод: Typical Programmer
@impwx
карма
119,0
рейтинг 1,0
Программист
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (113)

  • –84
    ахахах ржунимагу, соседей позвали сидим над заголовком угораем, участковый зашёл от смеха табельным мне мезицен на ноге прострелил, сижу в травмпункте пишу с утки
    • –72
      Ясно, с пониманием шуток у вас тут всё отлично.
      • +57
        С шутками тут все хорошо, а за глупости — наказывают.
        • –46
          А хотел всего-лишь — поднять настроение.
          • +59
            Что-то пошло не так.
          • +9
            Со стилистикой слегка промахнулся.
      • –1
        Тут давно всё плохо.
    • +21
      утка-то здесь причем?
      • +1
        Он с нее пишет
        • +3
          Бедное животное.
  • –38
    Функция getCustEmail была унаследована от функции getCustName, когда в приложении появилась поддержка email-адресов. Наследование кода подобным образом позволяет избежать случайного введения в программу новых багов.


    Вы обзываете наследованием простое копирование кода с внесением правок, чем извращаете саму эту идею. Ваш пример — максимум рефакторинг, и то какой-то бессмысленный и беспощадный.
    Наследование же как раз наоборот позволяет избавиться от дублирования кода и избежать потенциальных ошибок.
    Если бы вам предоставили, скажем, готовый класс CustInfo с полем custRec и методом getCustName(custRec), а вы унаследовали от него свой класс MyCustInfo и расширили его методом getCustEmail(custRec) — вот это примерно то, что обычно подразумевается под наследованием.
    • +26
      С чувством юмора у Вас не особо…
      Пожалуй, помогу:

      irony
      • –25
        Очень уж тонкая грань между юмором и ересью получилась.
        • 0
          Невероятно тонкая, почти прозрачная, не разглядишь)) <да да сарказм)>
    • +37
      Серьезно? А это единственное, что вас смутило :)?
      • +5
        Нет, дальше я уже всерьез не воспринимал :)
        • +21
          Дальше? «Ущербно-ориентированное программирование» вас на мысль не навело?
  • +24
    Ха! Да что они знают о наследовании!
    Мне проект достался с большим количеством, скажем так, нетривиальных архитектурных решений. Пять Ruby On Rails приложений, в каталог lib каждого симлинком залинкованы файлы из общего, над ними, каталога shared_libs. Но! Некоторые файлы были целиком скопированы и в них были внесены некоторые небольшие изменения, специфичные для конкретного приложения! Вот это я понимаю — наследование.
  • +11
    Боюсь если покажу эту статью своим менеджерам они не поймут юмора и чего доброго потребуют соблюдения вышеописанных принципов. Жисть не легка однако порой…
  • +8
    Очень хорошая сатира получилась. Про написание документации за две недели до увольнения это просто шик.
  • –9
    «УОП» имеет еще одно толкование — Убейтесь ОПчтонибудь
    • –1
      заминусовали адепты УОП? :)
  • +6
    Ага! Знаем, используем, продвигаем! :) Спасибо за настроение!
  • 0
    Надо будет на досуге пародию с уклоном в системное программирование написать
  • +22
    Ваша статья напомнила мне вот этот classic WTF:

    enum BOOL { FALSE, TRUE, FILENOTFOUND }

    Спасибо за поднятое с утра настроение!
    • +3
      Шикарно!
      • +1
        Полный код (Visual Studio 2015).
        image
  • –5
    Относительно перегрузки. Иногда правильнее будет сделать функцию с одним запросом в БД, которая вернет все поля, чем писать три (или более функции) в каждой из которых будет по запросу. Естественно, если требуется получить более чем одно поле.
    • +1
      Иногда правильнее работать с данными как с объектом, а объект — шарить. Иногда правильнее два раза в базу сходить. Иногда ещё совсем неправильно возвращать все поля из базы, потому что там бинарник на 100 килобайт или вообще, зачем тебе информация, которая тебе никогда не будет нужна. А иногда ещё…
      • +2
        Иногда правильнее не отвечать на глупые комментарии
        • +4
          И придерживаться своих же советов.
    • 0
      lazy load же
  • +5
    Мне кажется важную практику упустили
    Continuous deploy
    Это выкладка кода на боевой сервер желательно копированием по ftp, если application серверов больше одного то для синхронизации можно делать копирование силами нескольких участников на счет три. Последующие исключения исправляются по мере их немедленного появления или в понедельник утром, так как традиционно процедура деплоя назначается на конец рабочего дня пятницы, поскольку производительность разработчиков в это время снижается из-за усталости и кодить они толком не могут.
    • +2
      Уместнее здесь было бы про затянутое внедрение написать, наверное :)
    • +8
      О чем вы говорите, о каких исключениях?

      try
      {
         ....
      }
      catch 
      { }
      
    • 0
      В CI есть такая практика как Extremal Continnuous Deployment, когда каждый прошедший билд тут же идет на боевой сервер. Но естественно, следует понимать чем это может быть чревато. Кому интересно, могут почитать блог человека практикующего такой подход.
      • 0
        Ну так да, автотесты же бесполезны, а тестировщики всё равно что-нибудь да пропустят, какая разница.

        А вообще — подход хороший, когда есть master/devel ветки продукта, и клиенты, осознающие ради чего они пользуются devel-версиями и включают автоматическое обновление.
        • +4
          Кстати про тестирование тоже не слова:
          Тестирование
          Проверка работоспособности приложения заказчиком после релиза. Традиционная методика в данном случае отправка разработчикам приложения сообщения «Ничего не работает» или аналогичный звонок. Хорошим тоном является отправка ответного сообщения «На моей машине все работает» после чего уже может начатся конструктивная фаза.
          • +8
            Тогда ещё добавим пару слов об отладке :)

            Отладка
            Баг записывается в jira и задача становится отложенной.
            • 0
              Какая jira, о чем вы? У нас до сих пор потерялся листочек с 8ю некритичными багами с неиспользуемого модуля одного старого проекта…
    • 0
      Вы ещё забыли пункт про скрипт (автоматизация же), который конфиги копирует во временную папку, потому что новый код их затерёт, а потом обратно копирует поверх нового кода.
      • 0
        Вы напомнили одно чудище, существующее в одном моём проекте, которое хитро делает merge конфига из нового RPM-пакета с тем, что ввёл пользователь. Жаль альтернативы более адекватной не могу найти, может посоветуете чего?
        • 0
          Хранить настройки в Mercurial, из RPM — помещать в одну ветку, свои изменения — все в другую, после обновления делать слияние?

          В крайнем случае дописать к этому свой mergetool: при наличии общего предка слияние не должно быть слишком хитрым.
          • 0
            Слияния делать нежелательно, т.к. в конечном счёте это приводит к полной или частичной потере существующего конфига или в недоконфигурировании вновь созданных параметров.
            • 0
              Потеря с VCS?! Кроме того, weirded спрашивал, есть ли что‐то для слияние настроек, подобное тому, что он видел. Я указал альтернативу, которой сам пользуюсь. Очень частые изменения настроек: исправление ошибки в комментариях, единственный (он же по‐умолчанию) стал не единственным и был вынесен в настройку: с этим VCS отлично справляется. С бо́льшими изменениями есть больши́е шансы нарваться на конфликт при слиянии, но в этом случае вы будете видеть, какая часть файла была написана человеком, а какая — пришла из RPM.

              Кроме того, если настраиваемый компонент важен, то вы должны проводить тестирование. Возможность review после слияния тоже никто не отменял. Да и если вам не хочется полагаться на VCS, вы можете заставить VCS всегда сливать с помощью vimdiff. При этом вы опять же будете видеть, какие настройки пришли откуда.
              • 0
                Если бы видел, я этот ужас писал.

                Конфиг в виде ассоциативного массива на bash, сразу же с описанием виджетов для отображения в консольной менюшке на dialog. Пример: github.com/strizhechenko/bash-config/blob/master/config

                А мерджит его вот такое чудище: github.com/strizhechenko/bash-config/blob/master/merge_config.sh

                Вы предлагаете пользователю делать vimdiff после слияния?:)

                • 0
                  Если пользователь взял и поменял конфиг в /etc, то он не только пользователь, но и администратор. Я не вижу ничего плохого в том, чтобы администраторы использовали vimdiff для проверки корректности слияния или для самого слияния.

                  Кроме того, если вы не хотите писать слияние по сложным правилам, специфичным для каждого пакета (и при этом справляться с проблемами вроде «что делать, если пользователь пропустил пару‐тройку [десятков] релизов?»), то единственная вещь с алгоритмом, работающим для всех текстовых файлов, которая мне приходит в голову — это VCS.
                  Её же можно заставить использовать для слияния конфигов ваш скрипт, если нужно, при этом не рискуя испортить файл настроек навсегда (что возможно, если не делалось резервное копирования, а в вашем алгоритме слияния ошибка).
        • 0
          В принципе, вы описываете некую реализацию миграций. Вообще, это именно через миграции и делается — если есть дефолты, добавляем в рабочий конфиг дефолты, если есть настройки, для которых обязательно нужно выставить значения — у пользователя запрашиваются значения этих настроек. Т.е. по сути, то же самое, что при установке.

          Но это хорошая практика. В отличие от попыток «защитить» конфиг, вместо того, чтобы использовать какой-нибудь версионируемый config.default и игнорируемый config.
      • 0
        Зачем временная папка? Достаточно переименовать файлы, а потом обратно.
        • 0
          Нет, это уже слишком хорошая практика. Как известно, лучшее — враг хорошему.
  • +10
    В раздел «Документация» стоило бы добавить пару рекомендаций по написанию этой самой документации.

    Как понятно из названия, документация должна документировать код. При этом нужно избегать следующих малораспространённых (к сожалению) ошибок:
    1) Не нужно писать, что по задумке автора должен делать этот код. Вдруг задумка или требования поменяются.
    2) Не нужно описывать, что делает код в целом. Любому программисту это должно быть понятно из кода. Лучше сосредоточиться на частностях и деталях, например:
    for (int i = 0; i < sz; ++i) { // цикл от 0 до sz
    4) Никогда не нужно писать в комментариях, где и как используется данная функция или переменная. Это всегда можно выяснить поиском по проекту.
    5) Не следует описывать параметры функции и возвращаемый результат. Это и так интуитивно понятно любому, кто использует данную функцию. В любом случае, достаточно того, что это понятно автору кода.
    • 0
      И если вы вдруг пишете систему документирования, а она в ранней альфе и всё валится, то нужно документировать именно в ней, чтобы программист мог отвлечься от монотонного кодинга на светлые мысли о суициде. Потому что ведь двух зайцев сразу убить — и документацию написать, и систему потестировать.
  • 0
    Псевдокод можно было не писать. У каждого свои жуткие ассоциации с данными примерами :) Особенно смешно вышло с наследованием, а дальше уже слегка мутно
  • 0
    Главное, чтобы код был интуитивно понятен.
    var name1 = name + "1";
    var name2 = name + "2";
    var name3 = name + "3";
    
    if ( doc.Bookmarks.Exists( name1 ) && 
         doc.Bookmarks.Exists( name2 ) && 
         doc.Bookmarks.Exists( name3 ) ) {
             // ...
    }
    
  • +1
    «4/15/96 git» — так с 96 года, получается, такой код живет!
    И впрямь долгосрочное использование.
  • +3
    Про недостижимый код бы еще

    function SomeFunc(...){
        ....
         return $a ? true : false;
    
         throw new Exception('Пыщь пыщь алярм!');
         return false; 
    }
    


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

    If(false) {
    
    ... 9000 строк кода ...
    
    }
    
    • 0
      Подход if(false) { ... } иногда может быть полезен, поскольку код в нем все равно проверяется на синтаксическую и семантическую корректность. В результате исключается ситуация, когда код нужно срочно раскомментировать, а он не компилируется, потому что кто-то отрефакторил использованные в нем классы \ методы.
      • +1
        Да, в статье сильно не хватает пункта про комментирование ненужного кода, т.к. вдруг он снова понадобится, а искать в git'е долго и неудобно.

        Ну и никаких тестов и деплоев, конечно. Только хотфикс, только на продакшне, только под рутом.
        • 0
          через vim, а особо срочные задачи — через echo >>
          • 0
            Ну и, как уже говорили про деплой, это тоже только в пятницу вечером или в субботу ночью, с мобильника в клубе.
          • +1
            Иногда и через echo >, а потом случается «ой!».
  • +5
    Я бы еще вспомнил про «суперкласс». Суперкласс — это самый важный класс в системе. Без окружения он не имеет смысла. Он знает всё. Он самый большой. Его чаще всего меняют. Он имеет непредсказуемое название (например Binder или ShoppingCart). В момент его рождения никто не мог предсказать то, что это будет ключевой компонент системы.
    • +1
      Ну зачем придумывать название. Это же классический User.
      • 0
        it depends
        • 0
          it all depends… on User.
    • 0
      Ха! Я как-то в нашем проекте такой суперкласс вводил — с целью упрощения дальнейших изменений. До моего рефакторинга тем же самым занималось около 30 классов, подписанных на события друг друга, причем подписывать их надо было в строго определенном порядке. Диаграмма последовательности с трудом уместилась 10м шрифтом на лист A4.

      Как называется такой паттерн? Я бы предложил черный ящик — потому что ничего не понятно, и внутри что-то тикает, но это название в статье уже занято.
      • 0
        GodClass?
        • 0
          Нет, я про ситуацию с 30 классами, которые можно менять только совместно.
          • +2
            Объектно-событийная модель? Объекты сами должны знать, каким объектам они посылают события, от каких объектов они событий ждут, в каком порядке это должно происходить и зачем. Для повышения эффективности семантика очередного события может меняться в зависимости от предыдущих. Предсказать, как будет происходить процесс, программист не может, но это и не нужно: ведь всё работает правильно.
          • 0
            Сильная связанность.
      • 0
        Минус: сильная связанность. Но по сравнению у god-entity абсолютно такой же минус.
        Плюс: проще дебажить (имхо) из-за того, что сразу понятно, в какой функциональности сбой.

        Вообще, тут есть вопросы. Посмотреть бы на эти классы.

        Но то, что сделали вы (функциональность 30 классов в одном) — это уже даже слишком плохо, чтобы обсуждать.

        Взять какую-нибудь Symfony2 Standard Edition и диаграмму классов нарисовать, а потом ещё классы посчитать. Будет сильно страшнее, чем вы написали.
        • 0
          Но то, что сделали вы (функциональность 30 классов в одном) — это уже даже слишком плохо, чтобы обсуждать.
          Откуда вы там функциональность 30ти классов-то взяли и почему нельзя обсуждать?
          Во-первых, функциональность в классах не измеряется — они у всех разные.
          Во-вторых, там этой функциональности всего на 6 классов набежит, не больше.
          В третьих, дальше планировалось получившийся код разбить по-новой, но, так как он вдруг стал правильно работать, я решил его дальше не исправлять…
          • 0
            До моего рефакторинга тем же самым занималось около 30 классов
            • 0
              Но кто сказал, что эти 30 классов были нужны?
              • –1
                Вы. Вы же не сказали, что этим занималось, скажем, 10 классов.

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

                Ну и, как говорится, «happy debugging, guys».
    • 0
      Самое страшное — когда этот класс имеет название Form1
      • +1
        Не правда, самое страшное — когда этот класс имеет имя Utils.
        • 0
          Самое страшное

          textBox1
          textBox2
          textBox3
          textBox4

          textBox46

          и ручная выгрузка их значений в модель данных.
  • +4
    Приветствуются комментарии (обязательно на русском) в стиле:
    // пробуем авторизоваться из запроса
    // не получилось, бывает
    // тогда попробуем из кук
    // та же беда
    // используй силу, Люк! перенаправляем на главную
    // эта строчка очень нужна, поверь мне, друг
    // на поиск этого решения я потратил 6 дней, СДОХНИ, БАГ!
    


    Ну и обязательно (для удобства коллег и поиска по коду) нужно префиксировать всё:
    fnFoo — метод и функция,
    aRequest — массив
    oUser — объект
    sName — строка


    И называть надо всё как можно понятнее, т.к. не все по-английски говорят:
    shoppingCart
    korzina
    • +1
      Мне «нравится» вот такой стиль именования

      public void ProcessCustomerEntity(CustomerEntity customerEntity, UserEntity userEntity1, UserEntity entity2) 
      {
         ...
      } 
      


      Всё ведь понятно, правда? =)
      • 0
        А что именно здесь непонятно? Тут сами параметры метода выглядят странными. Есть подозрение, что в комментарии просто постараются оправдать это.
        • 0
          Ни одно название не передает никакого смысла в контексте бизнес-логики.
          • 0
            function createInvoice(Account $account, $amount)
            {
               ...
            }
            


            Или я вас неправильно понял?

            P.S. У каждого уровня приложения своя бизнес-логика. Одну диктует менеджер проекта, другую — техдир, третью — сам программист.
            • 0
              то что, не понятно что это за аккаунт.
              createInvoice(Account $customer, Money $amount)
              {
                  ...
              }
              
              • +1
                Почему это непонятно? Для сферического метода в вакууме — да. Но кроме этого у вас ещё есть класс, пространство имён и модуль. Мне обычно этого достаточно, чтобы не комментировать метод банальным «Создаёт инвойс для аккаунта плательщика на заданную сумму».

                Аккаунт не может быть клиентом. Деньги не могут быть количеством. Плюс, не факт, что мы работаем именно с аккаунтом клиента, а не с любым аккаунтом. Если хочется работать с аккаунтам клиента — используйте специфический класс, например CustomerAccount.

                То, что написано в вашем примере это:
                а) Двусмысленность — класс параметра говорит, что это аккаунт, а имя параметра говорит, что это клиент (например, пользователь или какая-то другая сущность).
                б) Конкретизация, требующая в таком случае дополнительной конкретизации (имя метода перестаёт соответствовать принимаемым параметрам). В моём примере предполагалось, что эта неопределённость разруливается именем класса, в котором реализован метод и именем неймспейса, в котором реализован класс.
                • 0
                  Аккаунт не может быть клиентом.

                  Не факт, зависит от потребностей домена.
                  Плюс, не факт, что мы работаем именно с аккаунтом клиента, а не с любым аккаунтом.

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

                  martinfowler.com/eaaCatalog/money.html.
                  • –1
                    Не факт, зависит от потребностей домена.

                    В таком случае, странно при наличии договорённости, что клиент — это счёт, относить его к классу Account.

                    Либо Customer $customer (в нашей системе любой счёт — клиент), либо Customer extends Account (в нашей системе любой клиент — счёт, но не любой счёт — клиент), но в итоге всё равно Customer $customer.

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

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

                    Счёт фактуру может создавать кто угодно кому угодно. Это зависит от специфики сайта. Зачем конкретизировать то, что делает итак вполне конкретную вещь — выставляет инвойс на нужную сумму нужному аккаунту. Я не говорил, что это выставление счёта клиенту.

                    Т.е. ваш пример — просто конкретизация на основе каких-то ваших предположений. Я эти предположения не делал.

                    martinfowler.com/eaaCatalog/money.html.

                    Об этом и говорю, деньги — это не количество, а количество в определённой валюте. Разная смысловая нагрузка. Количество в определённой валюте — это Money $money. В данном случае метод может изначально принимать сумму, изначально сконвертированную в валюту счёта. Т.е. это тоже некие предположения, о которых речи не шло.

                    Что получаем в итоге — вы в конкретном методе, выставляющем любому счёту инвойс на конкретную сумму (валют в системе нет), прочитали метод, выставляющий инвойс клиенту на конкретную сумму в произвольной валюте.

                    Разве это беда параметров метода? Это беда разгулявшейся фантазии.
                    • 0
                      в нашей системе любой счёт — клиент.
                      в нашей системе любой клиент — счёт, но не любой счёт — клиент

                      Про это и речь. Все зависит от потребностей домена.
                      Счёт фактуру может создавать кто угодно кому угодно.

                      Счёт-фактура выставляется продавцом покупателю. Wiki.
                      конкретную сумму (валют в системе нет)

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

                      Простите если жестко, но только вашей. Я привел абстрактный пример. Вы сделали из него выводы относительно вашей системы.
                      А вообще спорим мы не о том. Я в примере показал только то что именовать аргументы в соответствии с именами их классов, имхо, масло-масленное и дурной тон.
                      • +1
                        Счёт-фактура выставляется продавцом покупателю. Wiki.

                        А вы говорите, что счёт выставляется клиенту.

                        Если счёт всегда выставляется покупателю, то зачем конкретизировать, что счёт покупателя? Или всё-таки продавца? Или всё-таки я говорил о системе, когда клиенту моего сервиса выставляется счёт и в этом случае уточнение, что счёт клиента не имеет смысла вдвойне? В этом конкретном методе я этого не уточнял. Возможно, уточнение есть в имени класса или неймспейсе.

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

                        Т.е. у нас есть виртуальная валюта, не пополняемая извне, но мы всё равно таскаем везде валюту? Бессмысленно.

                        Я привел абстрактный пример. Вы сделали из него выводы относительно вашей системы.

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

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


                        Именовать аргументы классом — это хорошая практика:

                        \Dbal\Driver\Configuration $db — плохо
                        \Dbal\Driver\Configuration $dbConfiguration — хорошо

                        string $country — плохо
                        string $countryName — хорошо (уточняем класс строки — имя, хоть он и не является отдельным классом в коде)
                        при этом $countryString — плохо (но лучше, чем $country), т.к. всё ещё не даёт понимания, что это за строка.

                        Account $customer — плохо,
                        Account $customerAccount- хорошо.

                        UserGroup $owner — плохо,
                        UserGroup $ownerGroup — хорошо.

                        addUser(User $user) — хорошо,
                        setAccount(Account $account) — хорошо,
                        setCustomerAccount(Account $account) — хорошо,
                        setCustomerAccount(Account $customerAccount) — хорошо.

                        Часто оказывается, что и
                        User $owner — плохо
                        User $friend — плохо
                        если в системе присутствуют классы, вводящие двусмысленность (UserGroup, UserFriend, и т.п.)

                        В итоге именование с постфиксированием классом объектов всегда является более выгодным решением, т.к. в случае коллизии вам либо всё равно придётся играть с уточнениями постфиксированием, но будет каша, либо в итоге всё вообще встанет с ног на голову:
                        function createInvoice(Account $customer, $amount)
                        {
                           $user = $customer->getUser();
                           Mailer::create()->send($customerUser->getEmail(), 'You have new incoming invoice.');
                        }
                        


                        В моём варианте это будет выглядеть так:
                        function createInvoice(Account $account, $amount)
                        {
                           $user = $account->getUser();
                           Mailer::create()->send($user->getEmail(), 'You have new incoming invoice.');
                        }
                        


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

                        $user
                        $userAccount
                        $userGroup
                        или
                        $account
                        Money $accountBalance (интуитивно понятно)
                        Currency $accountCurrency
                        Contacts $accountContacts
                        • +1
                          А вы говорите, что счёт выставляется клиенту.

                          Прочитать по ссылке вы не удосужились видимо:
                          Счёт-фактура выставляется (направляется) продавцом (подрядчиком, исполнителем) покупателю (заказчику) после окончательного приема покупателем (заказчиком) товара или услуг. © Wikipedia.
                          А customer это как подсказывает Google Translate клиент, заказчик, покупатель.
                          Вы в ответ на мой пример привели свой, уточнив, что в моём примере непонятно, что это за аккаунт.

                          Не понятно аккаунт от которого выставляется счет фактура, аккаунт которому, или может это вообще корреспондент через которого?
                          Именовать аргументы классом — это хорошая практика

                          Это только ваше имхо и не более. Специально открыл посмотрел Э. Эванса по этому поводу и что то не увидел такой практики при построении домена. На уровне приложения да, есть такое, но не в домене.
                          • 0
                            Прочитать по ссылке вы не удосужились видимо:
                            Счёт-фактура выставляется (направляется) продавцом (подрядчиком, исполнителем) покупателю (заказчику) после окончательного приема покупателем (заказчиком) товара или услуг. © Wikipedia.
                            А customer это как подсказывает Google Translate клиент, заказчик, покупатель.

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

                            Я как раз считаю, что для моего конкретного сервиса у счёта-фактуры могут быть разные получатели — мои пользователи, я сам, некие лица, которые не являются частью взаимодействия я <--> пользователь и т.п.

                            Т.е. в итоге вы зачем-то сначала уточнили, чей счёт (вам это было почему-то непонятно), потом сказали, что уточнения излишни. Странно.

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

                            С абстракиями вы знакомы? С интерфейсами?

                            interface InvoiceInterface
                            {
                                public function createInvoice(Account $account, $amount);
                            }
                            
                            class InvoiceToUser implements InvoiceInterface
                            {
                                public function createInvoice(Account $account, $amount)
                                {
                                    ...
                                }
                            }
                            
                            class InvoiceFromVendor implements InvoiceInterface
                            {
                                public function createInvoice(Account $account, $amount)
                                {
                                    ...
                                }
                            }
                            


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

                            Единственное, чего я не могу понять, почему в вашем примере нет кода услуги. Ведь от этого налоги зависят.
                          • 0
                            Сначала поймите разницу между доменом и его реализацией. Потом перечитайте Эванса и подумайте, всегда ли надо уточнять, с каким счётом вы работаете и не следует ли это автоматичеки из домена или модуля, в котором идёт реализация этой функциональности. Т.к. как раз Эванс говорит о том, что всё лишнее надо выбрасывать.

                            Понимать, что $customer — это не клиент, а его счёт — лично для меня, лишнее.

                            И не путайте тайп-хинтинг с логикой.

                            P.S. Ничего вы так «посмотрели» 400+ страниц за полчаса.
            • 0
              ваш пример почти идеален =)

              чтобы было понятнее, чуть уточню:
              function createInvoice(Account $issuer, Account $billTo, $amount)
              


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

                Далее, не $issuer, а $issuerAccount, и не $billTo, а $receiverAccount. Ваше именование, по-моему, неоднозначно.
                • 0
                  Честно говоря, так глубоко влазить в рассуждения я не планировал.

                  $billTo — это англоязычная терминология, имеющая отношение к инвойсам. Если вам хочется написать $billToAccount — ради бога, если ваша IDE не дает полного понимания, на объект какого типа вы смотрите в данный момент, или по иной причине.

                  В примере, который я привел в начале треда, вместо $billToAccount было бы написано $accountEntity.

                  Можем отвлеченно порассуждать на тему, нужно или нет указывать $issuer в методе в контексте stateless classes :-)
                  • 0
                    IDE понимание даёт. Но в вашем случае надо знать предметную область (на английском), чтобы быстро разбираться в коде. В моём случае — код будет понятен любому. По сути ведь можно ввести, что user1 — это всегда объекта, а user2 — это всегда субъект. Такая вот договорённость, но не каждый сразу поймёт.

                    В данном случае человеку придётся разбираться — billTo — это счёт того, кому придёт платёж или кому нужно оплачивать.

                    Вообще, не очень люблю, когда код переполнен бизнес-лексикой. Если есть вариант использовать общеупотребительные аналоги, стараюсь использовать их. В этом плане payeeAccount — счёт плательщика, а receiverAccount — счёт получателя. Насколько помню, при оплате на сайтах встречал эти термины. Гугление дало использование этих терминов, например, UPS.

                    Но я не настолько много работал в узких областях, так что это, скорее, специфика моего мироощущения.
              • +1
                Но в итоге мой (не надуманный, а вполне реализованный на этой неделе) метод выглядит так:
                public function createInvoice(Account $payeeAccount, Account $receiverAccount, Currency $currency, $amount)
                {
                   ...
                }
                
                • 0
                  Этот код кардинально отличается от приведенного мной =)
                  Спасибо за дискуссию.
                  • 0
                    Да-да, я неправильно понял одну из ваших фраз.
      • 0
        Да, я вас неправильно понял. Я думал, вы про бизнес-логику в целом говорите, а не про конкретный пример, который вы описали. Ну, собственно, с этим я согласен. Как именно обработать и зачем нужны две этих сущности из именования непонятно.
    • 0
      Вот кстати про последнее: недавно видел код сайта бесплатных объявлений, где таблица объявлений называется tbl_objavlenia
      При этом таблица с данными авторизации называется tbl_users
      Хоть какого-нибудь стандарта/стиля придерживались бы, что ли…
      • +2
        Пфф
        image
        • 0
          Сурово…
        • 0
          Ну, это, видимо, какая-то подработка одинэсника.
        • 0
          Наличие двух одинаковых таблиц еще простительно, наименования — вещь на любителя, но за varchar без n поубивал бы…
          • 0
            varchar(100)

            Так что успокойтесь и положите топор на место.
            • 0
              Вы не поняли — речь идет не про длину, а про широкие символы.

              varchar — это последовательность байт в хрен знает какой кодировке. Под линуксом уже научились использовать utf-8, но на скриншоте приведен виндовый клиент к виндовому же Microsoft Sql Server — а значит, ад с кодировками и локалями, системными и БД, гарантирован.

              nvarchar — это последовательность байт в кодировке UTF-16 (или UCS-16). Пока не захочешь писать в базу данных иероглифы — никаких проблем с национальными символами не будет. Да и иероглифы с большой вероятностью успешно пройдут.
          • 0
            А в каких СУБД varchar может быть без размерности? С другой стороны, «безразмерный» varchar имеет длину, равную максимальной длине varchar'а в системе. Не думаю, что это хуже неким «256 символов». Почему 256, кто это посчитал, что это за класс строки такой, что ему нужно именно 256, а не 200, 140 или 70.

            Тут вот, например, артикул в 100 символов. Есть ли разница, ограничен артикул 100 символами или дефолтом БД при условии, что varchar всё равно «резиновый»?
    • 0
      Мне достался проект из Германии. Я, как и автор оригинального текста, тоже занимаюсь тем, что беру нечто непонятное и оживляю. Так вот, там местами не только комментарии на немецком языке, а и имена переменных, таблицы в БД, полей. Но не всех, где-то половины.
      Видишь в таблице поле «schlafplatz» и понимаешь, что ну всё — приехали.
      • +1
        Борода и свитер стали постепенно мутировать в усики и китель?
        • +2
          Хуже. Rammstein стал нравиться.

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