Pull to refresh

Comments 86

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

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

UFO just landed and posted this here

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

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

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

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

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

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

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

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

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

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

Помню это обсуждение и пришли новые мысли.

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

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

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

Я тут не защищаю именно ООП, есть и другие подходы, например Rust где есть только структуры и методы, или Haskell где всё функции с типами. Каждому инструменту своё место.

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

public void Apply(int port);

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

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

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

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

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

Вот когда будет больше 3 параметров, тогда и можно делать. Что исходит из KISS/YAGNI, не нужно заранее делать, работает не трогай.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вот один из примеров, как можно обезопасить себя, от возвращения 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;
    }
}

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

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

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

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

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-ов не надо, хотя это уже зависит от записанного в словарь.

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

В шарповском dictionary метод TryGetValue именно с таким поведением был со второй версии. Но тут надо проверять результат функции, да и result будет страшным nullом, если ничего не нашлось. Как ни крути от проверок никуда не деться.

С null, на самом деле, есть нюанс. В современных версиях этот метод объявлен уже как:

bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value);

а это сообщает компилятору, что в случае возврата true в value будет не null. Т.е. такой код:

if(foo.TryGetValue("foo", out var value)) 
{
    value.BlaBlaBla();
}

он пропустит нормально, но вот если забыть сделать if то будет уже предупреждение о "possible null reference".

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Будет вместо проверки на null, проверка на наличие элементов. В чем разница? Как по мне, так никакой разницы нет, что сову об пень, что пнем об сову)

Идея в том что null это «не правильный элемент» который делает «неправильный мёд»

В большинстве языков вам придётся заморачиваться с возвратом объекта/Клемента другого типа: null вместо условного list

При этом пустой список это всегда «валидный» элемент. Код дальше может передавать его вызывающим функциям без нарушения структуры типов. Грубо говоря, если у вас функция которая вернула null находится на 5-уровне вложенности то всем 5 уровням надо как то реагировать на Null.

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

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

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

Идея в том что null это «не правильный элемент» который делает «неправильный мёд»

Идея-то сама по себе простая, вопрос насколько идея хорошая.

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

Это хорошая идея для операций над последовательностями, ну вот как LINQ, там передается Enumerable и можно строить цепочки методов хоть в 100 штук.

Но как быть с методами типа GetUserById? Семантика страдает, почему я получаю список из опционально одного элемента вместо нужного мне объекта? Да и от проверки никуда не денешься

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

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

Во-вторых,

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

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

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

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

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

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

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

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

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

Это дело вызывающей стороны решать что делать с результатом и как его интерпретировать.

Это дело вызывающей стороны решать что делать с результатом и как его интерпретировать.

Правильно. И это свое решение вызывающая сторона выражает в выборе вызываемого метода. Если она ожидает, что элемента может и не быть, то она вызывает вариант TryGetElement и проверяет возвращенное значение на true. Если же она считает, что элемент обязан быть, то вызывает метод GetElement, а на проверке результата может сэкономить. А уж вызываемый метод в случае неправоты вызывающего сообщит, что тот был не прав, возбудив исключение. Исключение будет обработано где-то выше по стеку высокоуровнемым методом, для которого не нужны подробности: например, важно знать только, что выполнение завершилось неудачей, а причина неудачи не важна.Так можно сэкономить на излишних проверках, которые почти всегда будут проходить.

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

Как-то так.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Кстати стал искать ссылку на исследование. Небыли такого эксперимента.

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

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

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

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

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

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

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

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

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

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

Ага теперь получилось «кто не спрятался я не виноват» то что все предупреждены не решает проблему с обработкой null.

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

При этом если это коллекции, какой-то результат фильтрации или агрегации, то не вижу никаких проблем, ведь ожидаемый тип легко пометить как Task<TResult?>.

не решает проблему с обработкой null

Собственно, а в чем проблема в вызываемом коде добавить проверку null, особенно если мы декларируем, что такой вариант возможен? В том, что так "завещал" дядюшка Боб? Или из Linq теперь выпиливать все методы *orDefault()?

Подскажите плиз, почему нельзя чтоб функция возвращала null?

Допустим это функция поиска и она ничего не нашла и, вместо объекта, вернула null. Что плохого в этом, а самое главное, как по другому то?

Нет никакой проблемы. Просто надо избегать злоупотребления возвращать null где только можно, если не знаешь, что возвращать. Просто многие разработчики услышали это и восприняли слишком буквально, и лепят куда только можно без понимания контекста. Собственно, как и с SOLID, как с GoF паттернами, не сильно вникая для чего это и почему именно так.

Не то чтобы прям плохо. Но вот проблемы, которые из-за null часто возникают:

  1. null - может быть валидным значением в контексте поиска. И тогда непонятно - этот null есть в коллекции, или его таки нет.

  2. Принимающей стороне - может быть неочевидно из сигнатуры, что null может быть возвращен, в результате, клиент - не проверил, радостно залил в прод, и через 5 лет все упало, почему - хз. Поиск этого NRE может оказаться не таким тривиальным.

  3. Если даже никто ничего не забыл. Исходя из того что любой ссылочный тип - может быть null - ты должен как параноик - на каждом шагу проверять. И код превращается в бесконечные проверки на этот самый null

Варианты что делать: как договорились в команде.

Если в команде - ок с null - возвращайте-проверяйте. Если в команде принято кидать исключения - кидайте. Если у вас команда дофига любители ФП - Either<T, Error> какой-нибудь. Если любители ООП - какой-нибудь Result

Вместо get_power, можно кастовать army к типам power или count, а уж эти типы могут иметь оператор меньше.

Поздравьте меня, сегодня Роман заблочил меня на своём ТГ канале. Вероятно, из-за моих постоянных вопросов: "А где же собственно какая-то другая реакция кроме 👍" (пояснение: на любой пост в ТГ канале можно нажать реакцию на этот пост и обычно в других каналах этих реакций много, а тут ты можешь только согласиться ). Чуть статистики: на канале на данный момент 130К подписчиков, пост по сабжу набирает где-то 200-300 лайков, а общий пост, например, что вот сегодня он сходил в церковь около 900-1000. Выводы оставлю для развлечения читателя))

Мне кажется это проф деформацию Unity. Что в первом примере, что во втором. Может в юнити так принято перегружать операторы и использовать рефлексию направо-налево. Там всё странно, он как будто вообще не имеет ничего общего с C#, кроме синтаксиса. Взять хотя бы метод GetComponent<> никто в энтерпрайзе так не делает, иерархия наследования всего от MonoBehaviour, использование event, public/private. Там нет привычных DI, контроллеров, сервисов. Поэтому там скорее всего свои соглашения по хорошему коду.

Sign up to leave a comment.

Articles