Предыстория

Я познакомился с Романом, когда он публиковал на YouTube видео, в которых критиковал качество кода, написанного другими разработчиками, и создавал образ эксперта, который знает, как писать чистый и аккуратный код. На самом деле Роман действительно обладает такими знаниями, но то, как он их применяет, вызывает вопросы. Многие люди последовали за ним, стали использовать практики, которые он пропагандировал, и в целом доверяли его мнению. Затем Роман создал свою онлайн-школу для программистов, специализирующихся на C#. Я даже приобрёл его курс. Тогда я понял, что он учит не намного лучше, чем другие школы. Сейчас его курсы доступны для бесплатного ознакомления. Изучив их, мы увидим, что в его курсе реализация FSM (конечного автомата) слишком упрощена и не готова к использованию в реальных проектах.

О чем я тут раскидал буквы

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

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

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

В основном мы будем говорить не о том, как написана книга, а о том, какие вредные советы она может давать. А в конце обсудим, насколько полезна эта книга в мире, где есть такие издания, как:

  • Clean Code by Robert C. Martin. (Uncle Bob)

  • Clean Architecture by Robert C. Martin. (Uncle Bob)

  • CLR via C# by Jeffrey Richter

Вредные советы

Я буду приводить вредные советы по мере их появления в книге и объяснять, почему они вредны.

  1. Перегрузка операторов для кастомных типов в C#

    Как и другие операторы, эти операторы могут работать по-разному в отношении к разным типам. Так, например, мы можем создать сложный тип, который описывает армию игрока. И оператор меньше или больше будет сравнивать общую силу армии.

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

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

    Это будет выглядеть следующим образом:

    public struct Army
    {
        private readonly int _damage;
        
        public int Amount { get; }
    
        public Army(int amount, int damage)
        {
            Amount = amount;
            _damage = damage;
        }
    
        public static bool operator <(Army a, Army b)
        {
            return a.GetPower() < b.GetPower();
        }
      
        public static bool operator >(Army a, Army b)
        {
            return a.GetPower() > b.GetPower();
        }
    
        private int GetPower() => _damage * Amount;
    }

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

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

  2. Засорение аргументами
    Далее приводится пример того, как "можно" расширять код без риска его поломки при внесении изменений.

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

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

    public void Apply();
    public void Apply(int port = 80);

    Этот вариант имеет существенный недостаток: метод может начать принимать четыре, десять или двадцать аргументов, что противоречит принципам «Чистого кода» и приводит к созданию огромного и сложного метода, который берёт на себя слишком много задач.

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

    [Obsolete("It is legacy method, please use Apply(int port)")
    public void Apply();
    
    public void Apply(int port);

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

  3. Правила не передавай/возвращай null

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

    © Роман Сакутин "C# Для Начинающих на практике"

    В данном случае, мы в принципе нарушаем принципы "Чистого кода".

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

    ...

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

    © Robert C. Martin "Clean Code"

    То же самое касается и принимающей стороны. Зачем нам дополнительная проверка в алгоритме программы? И почему мы передаём null?

    Причина проста: однажды мы поленились сделать всё правильно, и теперь всегда будем писать лишний код.

    Можно возразить, что в приведённом в книге примере проверка будет выполнена только внутри метода. Вы правы, но добавление проверки внутри метода вступает в противоречие с принципами «чистого кода». Наш метод начинает изменять своё состояние в зависимости от определённого флага (в данном случае это будет сравнение аргумента на null), что нарушает правило об одной операции.

    Функция должна выполнять только одну операцию. Она должна выполнять ее хорошо. И ничего другого она делать не должна.

    © Robert C. Martin "Clean Code"

Вендинговый автомат

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

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

Действительно полезная информация

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

К таким вещам относятся:

  • Принципы S.O.L.I.D и то, как мы их используем и почему мы их используем.

  • Framework Roslyn - полезное API для написания собственных анализаторов кода.

  • Числа Фибоначчи - преведена целая глава работы с этими числами, что я считаю полезным для начинающих.

  • Работа с Reflection, но тут важно уточнить, что опять же, Роман не сообщат, что чрезмерное использование рефлексии, (может привести) приведет к снижению производительности.

Заключение

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

Исходя из заголовка книги "C# Для Начинающих На Практике", я отношусь к тексту этой книги более легко, если название было что-то типа "C# Для Заканчивающих На Практике".
Но это не дает права на те вредные советы, которые были приведены в книге!

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

А теперь, как и обещал, мое мнение, на сколько нужна эта книга в мире, где есть книги от Рихтера и Мартина. Ответ простой, не нужна, эта книга не открывает иного взгляда на программирование, она не ставит под сомнения идеалы и принципы устоявшихся методов разработки, она только показывает, что Роман сам может нарушать правила "Чистого кода", но при этом поучать других за модификаторы доступа.

По отношению к Рихтеру, то тут все еще проще, Рихтер пишет о том, как C# работает, а Роман пишет о том, что есть if и оператор == и исходя из этих данных, выгоднее один раз купить CLR via C# и если не прочитать его, то обращаться к нему в спорных моментах, что бы понимать, как язык на котором вы общаетесь с машиной, будет работать.

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

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


  1. Leopotam
    18.05.2024 09:15
    +17

    Какой "чистый код", он анонсировал курс по ECS, который 4 года назад яростно хейтил, причем на том же фреймворке, который и поливал тогда говном. Конъюктурщик чистой воды.
    Для любителей "чистого кода" в геймдеве (а не в разработке бизнес-приложений), где важна производительность, а не максимальное распиливание на абстракции - есть вот такое занимательное видео: https://www.youtube.com/watch?v=oFiY7EEtAW4


    1. nubowski
      18.05.2024 09:15
      +3

      для особо ленивых, и кто любит читать больше, чем видосики смотреть :)

      https://github.com/unclebob/cmuratori-discussion/blob/main/cleancodeqa.md


    1. Raindery
      18.05.2024 09:15
      +9

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


      1. shrum
        18.05.2024 09:15
        +2

        А можно подробнее?



      1. ryanl
        18.05.2024 09:15
        +2

        Хватит ерунду городить, с позиции своих 10+ лет опыта, посматривая от скуки его видосики, могу сделать вполне себе вывод - что Виндертон довольно квалифицирован, и делает очень классные концентрованные ролики по IT - самое-то для новичков или желающих вкатиться. Сакутин ему в подметки не годится (не сравнивайте шарписта и плюсовика, у плюсовика фундамент крепче). Не понимаю эти необоснованные комментарии - "не разговаривайте с ним, он плохой". Ей богу, как маленькие дети. По одному только качеству роликов можно сделать вывод, что контент очень и очень проработан и на уровне.


        1. Raindery
          18.05.2024 09:15
          +7

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

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

          Что касается его квалификации, это тоже сомнительный тейк. Так как, в то время как он делал ролики про то, как надо учится, CS и тд, он спрашивал совершенно глупые вопросы в Мейл.ру, StackOverflow и тд, что-то вроде: "Как можно вывести массив чисел в консоль".

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


        1. comradeleet
          18.05.2024 09:15

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


    1. Gay_Lussak
      18.05.2024 09:15
      +7

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


    1. idd451289
      18.05.2024 09:15

      Иии?) то что он 4 года назад спал технологию/язык/мнение не даёт ему право, попробовать, разобраться, понять что это может быть хорошей технологией и заанонсить курс?


      1. Leopotam
        18.05.2024 09:15
        +4

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


    1. egribanov
      18.05.2024 09:15

      Не в его защиту, но камон, 4 года. Вы же не будете спорить, что 4 года назад у него было столько же опыта как сейчас?


  1. gotoxy
    18.05.2024 09:15
    +2

    [Obsolete("It is legacy method, please use Apply(int port)")public void Apply();

    public void Apply(int port);

    Тоже сомнительное решение, ведь

    метод может начать принимать четыре, десять или двадцать аргументов

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

    Если уж хрен его знает, что там метод будет принимать в будущем, почему бы сразу не заложить parameter object?


    1. joe_krelli Автор
      18.05.2024 09:15

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


  1. tadaima
    18.05.2024 09:15
    +6

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


  1. Kahelman
    18.05.2024 09:15
    +8

    Вообще-то Чистый код и его автор Роберт Мартин тот ещё експерт. Не согласные могут рассказать что Роберт Мартин создал пользуясь своими идеями? Нашёл только ссылку на триста он консалтером работал.

    Давайте лучше не реальных классиков ссылаться: Ричи, Керниган, Строцстроп, Оустерхаут и другие.

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


    1. Atreides07
      18.05.2024 09:15
      +7

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


  1. piton_nsk
    18.05.2024 09:15
    +5

    То же самое касается и принимающей стороны. Зачем нам дополнительная проверка в алгоритме программы? И почему мы передаём null?

    А что надо возвращать? Давайте возьмем банальный пример - Find в коллекции. Если ничего не найдено, что должен вернуть метод? Для FindIndex можно договориться что -1 означает что ничего не нашли, но получается тоже самое - что проверять на null, что на -1.

    Что касается Мартина, читали его книгу про шарп, которая " Agile Principles, Patterns, and Practices in C# "?


    1. MountainGoat
      18.05.2024 09:15
      +3

      Возвращать нужно всегда

      union <type T> {
      T[] data,
      None
      }

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

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


      1. piton_nsk
        18.05.2024 09:15
        +5

         я теперь считаю, что исключения

        Причем тут исключения? Исключения штука своеобразная, куда попало совать не надо, но речь-то про другое.

        Возвращать нужно всегда

        Чем это отличается от null ?


        1. MountainGoat
          18.05.2024 09:15

          Чем это отличается от null ?

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


          1. piton_nsk
            18.05.2024 09:15
            +3

            Всего лишь тем, что это невозможно забыть проверить на null.

            Проверка один фиг нужна, никуда от этого не денешься. Или я что-то недопонял? Приведите плиз пример


            1. MountainGoat
              18.05.2024 09:15
              +1

              null value или error code, будь это void*, nullptr в С++ или None в Python, называют ошибкой на миллион. Потому что рано или поздно все всегда где-нибудь забудут его проверить, и в систему полетит заказ на -1 единицу товара или хеш нового пароля равный 0.

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

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

              Вот например как в Rust, но так можно сделать в Python. Можно и в С++, просто как всегда коряво выглядит.


              1. Onito
                18.05.2024 09:15

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


            1. Onito
              18.05.2024 09:15

              С std::optional можно не делать проверку и получать исключение если внутри ничего нет. Вроде это желаемое поведение?


          1. A1lfeG
            18.05.2024 09:15
            +2

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


        1. joe_krelli Автор
          18.05.2024 09:15

          Вот один из примеров, как можно обезопасить себя, от возвращения null

          internal class NotReturnNullExample
          {
              private readonly Dictionary<string, Data> _dataMap;
          
              public NotReturnNullExample()
              {
                  _dataMap = new Dictionary<string, Data>();
              }
              
              public TData GetData<TData>(string key) where TData : Data, new()
              {
                  if (!_dataMap.TryGetValue(key, out Data data) || data is not TData genericData)
                  {
                      genericData = new TData();
                  }
                  
                  return genericData;
              }
          }


          1. ryanl
            18.05.2024 09:15
            +5

            Код некорректен - если в словаре нету данного ключа, то создаем новую сущность и ее же возвращаем? Если в корзине нету красного яблока, создадим новое и вернем его. Где здесь корректность? Null-значения все же не просто так придумали, хе хе. Можно прикрутить сюда NullObject pattern (или как его там кличут).


            1. piton_nsk
              18.05.2024 09:15
              +2

              Можно прикрутить сюда NullObject pattern (или как его там кличут).

              Вообще говоря это он и есть, другое дело если словарь всегда возвращает результат, иногда только дефолтный, то это, мягко говоря, так себе идея. В том же Linq есть парные методы - Single и SingleOrDefault и так далее. А тут штука похлеще нулла.


          1. piton_nsk
            18.05.2024 09:15
            +2

            ну тут null заменяем на пустую коллекцию. Проверять на null вроде как плохо, а тут надо проверять пустая коллекция или нет. В чем принципиальная разница?


          1. Fitbie
            18.05.2024 09:15

            public bool GetData<TData>(string key, out TData result) where TData : Data
                {
                    result = default;
                    if (_dataMap.TryGetValue(key, out Data data) && data is TData tData)
                    {
                        result = tData;
                        return true;
                    }
                    
                    return false;
                }

            Всю жизнь делал так, собсна алгоритм списал у самого фреймворка. И никаких null-check-ов не надо, хотя это уже зависит от записанного в словарь.


            1. nronnie
              18.05.2024 09:15

              Ну это стандартный паттерн "Try-out". Только к имени метода принято добавлять префикс "Try..." (по-моему, иначе даже linter при достаточно "строгом" режиме будет ворчать на "out" параметр).


      1. AcckiyGerman
        18.05.2024 09:15
        +1

        Что в лоб, что по лбу что `Null`, что `None`

        union классный тип, но принимающий код придётся переписать, так что в любом случае мы опираемся на принятые в проекте умолчания. В Go повсеместно if err != nil и это вполне себе Industrial standard.


      1. gotoxy
        18.05.2024 09:15
        +2

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

        Вот только в шарпе существуют null reference type, который уже документирует, что можем получить null. И если не работать в блокноте, то шанс выхватить nullreferenceexception секретом не останется. Кроме того, есть занимательные операторы .? и ?? , так что не понятно, зачем городить огород из туплов/юнинонов, когда всё нужное уже есть в языке.


        1. nronnie
          18.05.2024 09:15

          Да, "nullable reference types" это реально прорывная штука. Проблема, правда, всё ещё остаётся в виде каких-нибудь ленивых говнокодеров, которые всегда могут его обойти через !. Плюс еще сторонний код, который "nullable annotations" вообще может не понимать (поэтому, например, даже штатный linter всегда настоятельно советует проверку входных параметров на null для публичных методов).


    1. idd451289
      18.05.2024 09:15
      +1

      Зависит от компании, от идей кода

      Например я вижу несколько решений. Либо optional интерфейс, как у джавистов, либо ошибку, либо null но я с явной пометкой. Слава богу новый дотнет даёт такую опцию. В теории можно возвращать default значение, но это совсем что то из странного мира. Хотя и такие кейсы имеют право жить, все зависит от проекта, от фреймворка, от команды


      1. piton_nsk
        18.05.2024 09:15
        +1

        В теории можно возвращать default значение, но это совсем что то из странного мира. 

        Это линковский FirstOrDefault

        Например я вижу несколько решений. Либо optional интерфейс, как у джавистов, либо ошибку, либо null но я с явной пометкой.

        Варианты разные, это да, но чем плох null, что его надо избегать?


    1. TrueRomanus
      18.05.2024 09:15
      +2

      Проблема null не в том что его надо избегать а в том когда метод который !не должен! возвращать null начинает его возвращать. Т.е. это уже перетекает в плоскость логики и ожиданий от вызывающего кода. Но тоже самое будет например в случае если метод возвращает int но четко написано что отрицательных значений не может быть а потом бац и возвращается отрицательное значение которое вызывающий код явно не будет ожидать.


      1. piton_nsk
        18.05.2024 09:15
        +1

        Давайте пример про Find в коллекции, что он должен возвращать если ничего не найдено?


        1. TrueRomanus
          18.05.2024 09:15
          +2

          Если написано в описании метода что он вернет null для кейса когда ничего не найдено то это полностью валидное использование null. Вызывающий код осведомлен о то что такое возможно и он должен обработать такой кейс (т.е. проверку на null) и это абсолютно правильное поведение. Т.е. метод Find(predicate) в его описании написано что if not found then null, if not founded item.

          Обычно проблемным считается случай когда в методе (по ошибке или по отсутствии документации или еще чему) вызывающий код не ожидает что там будет null. Т.е. метод Find(predicate) в его описании написано что if result will be foundede item.

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


          1. piton_nsk
            18.05.2024 09:15
            +1

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

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

            Если написано в описании метода что он вернет null для кейса когда ничего не найдено то это полностью валидное использование null. 

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


            1. TrueRomanus
              18.05.2024 09:15
              +2

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

              Мартин много про что не пишет :) И более того воспринимать его тейки буквально вне зависимости от здравого смысла такая себе идея. Принимать как рекомендацию да. Как я сказал выше проблема с возвратом null распространяется на любые типы данных, просто с null ошибка моментально даст о себе знать крашем в то время как другие типы выстрелят по другому. Примеры - метод возвращает int и ожидается положительные числа но вдруг приходит отрицательное, метод возвращает enum на 3 числа (1,2,3 допустим) а приходит 4. На самом деле вариантов такого много и моя мысль в том что обработка возвращаемых значений это нормальная практика. Проблема в том что часто не всегда и не везде хочется делать какие-либо проверки и запрет на использование null преподноситься своего рода панацеей с одной стороны делают ненужными написание проверок а с другой стороны избавляющего от крашей.

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

              Да обработка результата это нормально.


        1. Kahelman
          18.05.2024 09:15

          Как вариант всегда возвращать коллекцию элементов.

          Решает проблему если у вас больше одного элемента в коллекции -возвращаете все дальше дело клиента что с ними делать.

          Если элементов нет - возвращаете пустую коллекцию.


    1. Vasjen
      18.05.2024 09:15
      +1

      На Ваш вопрос есть ответ от разработчиков API Wildberries. Они смогли удивить. Во-первых,

      Технический перерыв в работе метода: каждый понедельник с 3:00 до 16:00.

      Во-вторых,

      Если нет данных за указанный период, метод вернет null.

      Эндпоинт возвращает коллекцию элементов, а если не найдено - то null. Почему не пустую коллекцию, раз нет элементов – вопрос открытый. Но самый прикол заключается в том, что возвращаемый null – это не null в привычном смысле, а строка с текстом "null". Гениальное решение!


      1. piton_nsk
        18.05.2024 09:15

        да уж)


  1. ryanl
    18.05.2024 09:15
    +1

    Притянутая за уши критика.

    То же самое касается и принимающей стороны. Зачем нам дополнительная проверка в алгоритме программы? И почему мы передаём null?

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


    1. ryanl
      18.05.2024 09:15

      Минусуете - тогда аргументируйте, господа, как у профессионалов на stackoverlfow; у нас ведь тут тоже вроде как профессионалы собираются.


  1. Ramayasket
    18.05.2024 09:15
    +2

    чрезмерное использование рефлексии, (может привести) приведет к снижению производительности

    Чрезмерное - ну да, наверное. Но я сталкиваюсь с тем, что молодые специалисты боятся рефлексию применять вовсе, такой страх им кто-то внушил. Можно поставить эксперимент, который покажет, что первое обращение к рефлексии определённого типа занимает миллисекунды, последующие - микросекунды. Вооружившись этим знанием, можно здраво судить, пострадает ли производительность ВАШЕЙ КОНКРЕТНОЙ программы, или она рефлексию даже не заметит. В последнем случае, рефлексия - великолепное средство, обладающее потенциалом весьма значительного сокращения кода.


  1. mvv-rus
    18.05.2024 09:15
    +1

    Причина проста: однажды мы поленились сделать всё правильно, и теперь всегда будем писать лишний код.

    Глагол у вас тут неправильный выбран: лень - далеко не единственная причина чтобы передать null. Хотя бы потому, что на практике сделать все правильно раз и навсегда удается не только лишь всем.

    А иногда так делать даже целесообразно. Как вам, к примеру, такой код

    public interface IRunner {
      //...
      public void Abort(String? TraceIdentifier = null);  
      //...там ещё несколько методов с таким же параметром, этот - простейший
    }

    Это - из интерфейса некой библиотеки, предназначенной для программ, использующих ASP.NET Core. TraceIdentifier здесь - это идентификатор запроса, предназначенный для трасировки (по записям в логе или еще как: ILogger - штука универсальная). И только в лог он и пишется (и то, если соответствующий уровень логирования включен). А логика реализации метода от этого параметра не меняется никак.

    Короче, это - для observability. Если вызывать этот метод из обработчика запроса HTTP, то в качестве аргумента тут осмысленно передавать HttpContext.TraceIdentifier. Но можно и не передавать: совершенно не факт, что потребителям этой библиотеки это самое observability понадобится, и я за них такие вопросы решать не готов. Так что тем, кому не надо, можно этим параметром не заморачиваться, вообще. То есть - как раз не писать тот самый лишний код.
    А ещё этот метод может быть вызван (и реально вызывается, внутри библиотеки) вне обработчика какого-либо запроса, когда HttpContext просто нет.

    А потому, что бы ни думали об этом теоретики, решение сделать именно так было принято и было принято осознанно.

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


    1. nronnie
      18.05.2024 09:15

      Что мешало просто сделать два перегруженных метода? И волки целы, и овцы сыты.


      1. mvv-rus
        18.05.2024 09:15

        Можно-то можно, но есть соображения, почему делать лучше именно так.

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

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


        1. nronnie
          18.05.2024 09:15

          То есть - придется иметь лишний код.

          Его и так придется иметь в виде какого-нибудь ветвления типа if(traceIdentifier == null) { bla_bla_bla; } внутри реализации методов.


          1. mvv-rus
            18.05.2024 09:15

            Нет, в данном конкретном случае - не придется. Этот параметр, в конце концов, идет в ILogger.Log (точнее, в LoggerExtensions.Log(ILogger,...) ) и никуда более - а тот прекрасно ест null в качестве параметра сообщения.


  1. EgorTolokonnikov
    18.05.2024 09:15
    +2

    Статья хорошая, но все эти разгоны про чистый код это пук в воду. На практике ты будешь делать так как делают твои коллеги и твои порывы — "А ДАВАЙТЕ НЕ ИСПОЛЬЗОВАТЬ NULL!" Будут со смехом отклонены)

    Да и в целом, не понимаю в чем проблема null'а, микромягкие в новых версиях все больше фишек с ним делают, не вижу смысла его игнорировать


    1. joe_krelli Автор
      18.05.2024 09:15

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

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

      Есть вещи которые уже устоявшиеся и они как правило записаны в code conventions компании и с ними бесполезно спорить, потому что так уже тупо принято и ты должен подстроиться под это, но всегда можно попробовать оспорить, если у тебя есть весомые аргументы.


      1. nronnie
        18.05.2024 09:15
        +1

        там скорее все же поднимут этот вопрос и будут обсуждать

        Никто ничего не будет обсуждать. Автора предложения смешают с говном и всё останется как раньше.



        1. joe_krelli Автор
          18.05.2024 09:15

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


      1. OldNileCrocodile
        18.05.2024 09:15

        А в ещë более здоровом коллективе ЗОЖников, новые идеи и доработки принято делать на копиях и в отдельных ветках. Но на практике криворучка Git-ом сливает коммиты, и теряет ветки.


  1. Scrawach
    18.05.2024 09:15

    Другими словами, всего три претензии на книгу по программированию в 300+ страниц? Слушайте, вы продали мне эту книгу.


    1. joe_krelli Автор
      18.05.2024 09:15
      +4

      Книга в 300 страниц не из-за большого кол-во текста, а из-за формата в A5 и большого шрифта, так же в электронном варианте есть страницы с 0-5 строчками.


      1. Scrawach
        18.05.2024 09:15

        Тем не менее, даже к вашей статье, которая несравнимо меньше 300-х страниц формата А5 и большого шрифта, у читателей возникло не меньше вопросов, нежели у вас к книге. Это говорит о качестве книги или статьи? В общем, почва для холиваров и обсуждений в программировании просто необъятна, поэтому на этом фоне "три вредных совета" смотрятся уж чересчур блекло. Уверен, что вредных советов там намного больше. Впрочем, как и в любой книге по программированию.


  1. Vasjen
    18.05.2024 09:15
    +3

    1. Правила не передавай/возвращай null

    Очень дискуссионное утверждение.

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

    В каких-то участках кода – охотно верю в это утверждение и соглашусь. Но в C# существует Nullable value types, благодаря которым мы всегда должны ожидать, что может вернуться null. А значит это не должно стать ни сюрпризом, ни проблемой. Уйти от этого можно путем создания каких-то моделек, каких-то перечислений с кодами / статусами,, но зачем?