Я стал думать над мелочами в коде, и уничтожил все желание программмировать



    На моей последней проектной работе мне предложили создать решение на .net/c# с нуля. Заложить архитектуру, стандартные либы, практики, и т.д. Приложение планировалось большое, я получил море бюджета на исследование и продумывание всего.

    В процессе я принял много решений. Так много, что сильно удивился — сколько же практик и подходов у меня сложилось за довольно короткую карьеру. Откуда я их взял? Как я к ним пришёл, и с хрена ли я в них верю?

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

    У меня в башке есть такой абстрактный «правильный разработчик», который всё делает единственно верным образом. Обычно моя задача быть немного на него похожим. Когда я пишу какой-то код, спрашиваю: «А как бы поступил правильный разраб? Что бы он выбрал и почему?». Да, его не существует, и он на самом деле никак бы не поступил, и эта мысленная сущность не помогает мне решать проблемы. Она нужна для двух вещей: заставляет задавать себе кучу вопросов и чувствовать себя куском говна каждый раз, когда хоть что-то сделал.

    С годами работы и я, и мой правильный разраб стали одинаковыми, и мы почти не задаём себе вопросов. Но говном-то я себя, конечно же, считаю, видимо, по привычке.

    Вот как это работает.

    Мне надо спроектировать либу — http client, для специфического реста. Я такой: «Окей, круто. Возьму последний C#, потому что я всегда беру последнее всё. Включу экспериментальные фичи, потому что скорей всего они будут актуальными в момент, когда я закончу разработку. Использую JetBrains Rider, потому что он выглядит более современным, чем студия. И начну писать код».

    Примерно так

    public interface Api {
    
    }

    И сразу тонна вопросов. Это публичный интерфейс либы, которая представляет апи сервера лицензий. Почему он называется не `LicenseApi`, а просто `Api`?

    Потому что проект называется HttpClient. А фолдер (сборка), в котором лежит интерфейс, называется License. А директория, в которой он лежит, называется Public. Я не хочу иметь название `HttpClient.License.Public.LicenseApi` — два слова `License` подряд — что хотел сказать автор? Сейчас доступ к интерфейсу будет выглядеть так: `HttpClient.License.Public.Api`. Кристально чисто.

    Го форвард. В дотнете принято называть интерфейсы с префиксом I. IApi. Эта штука даже не обсуждается — сложившаяся практика. Она пришла из тех времен, когда IDE нихрена умели, и разрабы хотели быстро понять, что перед ними — интерфейс, класс. структура, указатель… Моя сегодняшняя IDE может покрасить, подчеркнуть и обжирить любой символ любого ЯП ровно так, как я этого хочу. Райдер по моему приказу пишет интерфейсы курсивом. Зачем мне префикс? Во-первых, так просто принято, и у нас в разработке принято поступать так, как принято. Я отрастил достаточно большие яйца, что бы поступать по другому, когда категорически не согласен. Во-вторых, есть практический момент — дурацкий гитхаб. Он из коробки не умеет в символы, и, просматривая сорцы там, я не пойму, где интерфейс, а где класс. Здесь мой поинт таков — ну и хрен с ним. В гитхабе разработка не ведется, там отображаются её результаты. Хочешь понимать, где что — клонируй репу, открывай IDE и смотри. Ещё показательный момент — в официальных гайдлайнах к тайпскрипту не рекомендуется использовать префикс I для интерфейсов, аргумент — это пережиток прошлого.

    Лично для меня ключевой аргумент один — а чего это вы, парни, тогда не пишете префиксы для всего?

    
    public class CApi {               // C - класс
      private int _some;              // _ - field
      public IMyType PSome { get; }   // P - property
    
      public void MDoStuff(){..}      // M - method
      public event EventHandler MyEvent; // event, event, event
    }
    

    Зашибись же? По листингу кода всё станет понятно. Дерзайте.

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

    Тут возникает следующая проблема: дотнетчики вечно пишут `IApi`, что бы потом написать `Api: IApi`. В этом смысле, префикс спает их от придумывания ещё одного названия. Которое довольно сложно придумать, если ты для всего подряд хреначишь интерфейс с едиснтвенным наследником. Лечится довольно просто: тебе нужен интерфейс, только когда ты точно знаешь, зачем он тебе нужен. В таком случае у тебя два кейса — или у тебя действительно интерфейс-ентерфейс, или ты обходишь ограничения платформы.

    В первом случае проблем с названием не возникает, у тебя есть условно интерфейс `IDataProvider` и реализация `SqlDataProvider`. Во втором случае проблемы. Вот мой кейс с License.Api — начерта мне нужен интерфейс вообще? Штука в том, что я проектирую либу. Она будет использоваться в нескольких проектах, и в них будет n модулей, которые знают, как работать с моей либой. И если они захотят закрепить юнит-тестами факт, что их модули вызывают методы моей либы так, как они это задумали, им понадобится фигачить моки. А тут проблемы с платформой — в C# с моками всё очень плохо — если захочешь делать мок не на интерфейс, а на класс с не виртуальными свойствами и методами — пойдешь в жопу. Поэтому, если я буду поставлять публичную часть либы в виде класса, пользователи моего кода обречены создавать фасады, прокси, или ещё что — оборачивая мой интерфейс в свой. Это и без тестов иногда хорошая идея, но мне не нужно усложнять людям жизнь, если они сами не хотят её себе усложнить. Короче, мне интерфейс нужен, что бы обойти ограничения платформы.

    Я разрулился с названием очень просто. Я назвал класс ТОЧНО ТАК ЖЕ, как и интерфейс. Никогда так не делаете? Зря. Вот как это выглядит в коде

    
    internal class Api : Public.Api {
      //...
    }
    
    

    Они и должны называться одинаково, это одинаковые по смыслу сущности. Их отличает факт, что один интерфейс, а второй — реализация: факт, известный IDE и компилятору. Я использовал доступ к интерфейсу `Api` через `.Public`, чтобы разрулить коллизию, но не только за этим. Пространство имён — часть имени типа. По-моему, когда эстетика это позволяет, отличная идея это использовать. Сами посмотрите:

    
    public class Sample {
      Dto.User user;
    
      Util.Date dateUtil; // переменные этих типов называем как хотим
    
      Storage.Customer customerStorage;
    }
    

    К сожалению, в C# нет инструмента, чтобы заставить людей использовать в коде доступ с пространством имён. А вот в F# есть `RequireQulifiedAccess`-атрибут, вешаешь его на что угодно, и эту штуку можно будет забрать только указав последнюю секцию неймспейса, в которой она лежит. Этот атрибут охрененный, потому что помогает писать выразительное и понятное апи.

    Вернёмся сюда

    
    public interface Api {
    
    }
    

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

    
    public interface Api
    {
    
    }
    

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

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

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

    Тут главное не пойти дальше и не начать учиться у фронтендеров всему остальному. В какой-то момент я оказался настолько очарован возможностями системы типов в тайпскрипте, что подсознательно решил — фронты знают что делают. Надо учиться у них. На практике, создатели тайпскрипта — единственные люди во фронтенде, которые знают что делают. Остальные фронты, с которыми я работал, творят какую-то немыслимую дичь. Меня спустил на землю кейс, когда я захотел сделать приложение на электроне. А потом пять синьоров фронтендеров не смогли мне сделать так, что бы я его мог дебажить из IDE или текстового редактора. Они все настраивали мне дебаг ДЕСКТОП-приложения в браузере, и говорили, что так и надо. Сама либа electron в браузере не работает. Т. е. я мог дебажить одну половину приложения (без UI) в IDE, а вторую — в браузере. Их взаимодейтвие — нигде. И мне дали понять, что если меня это не устраивает — пошёл я нахрен. И я пошёл.

    Статья для дотнетчиков, так что, парни, давайте передохнём минуту и хорошенько поржём над «НАСТРОИТЬ ДЕБАГ».

    Ещё момент. Когда я вижу `}` — мне надо быстро понять, какую конструкцию она закрывает. Поняли? Конструкцию, а не `{`. У меня `}` находится в одном столбце с декларацией интерфейса, который заканчивается на этой скобке. Посмотрел на скобку, поднял взгляд, увидел, что она означает конец интерфейса. Всё.

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

    Например, мой подход со скобками экономит мне по строке на метод.

    Я хочу, что бы было так: открыл файл, окинул взглядом, всё понял. Что делает, как делает, зачем делает. Это недостижимо, но портянки на 500 строк отодвигают тебя от идеала на три миллиарда километров.

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

    Привычка писать небольшие файлы порождает другие. Я ненавижу, когда коллеги говорят: «Не надо плодить лишних абстракций!». Фраза абсолютно верная — лишнее не нужно. Проблема в том, что её часто понимают: «Не плодите абстракции». Так происходит потому, что отличить лишнюю абстракцию от нужной — тяжелый труд. В каком-то смысле, в этом и заключается наша работа. Но мозги не хотят трудиться, мы хотим одно решение, которое работает везде. Поэтому есть разрабы, которые фигачат портянки на тыщу строк, и есть такие, которые выделяют абстракции на каждый чих. Я не хочу выбирать из двух зол. Каждый раз, когда пишешь код, каждый конкретный раз тебе придётся думать заново. Нет и быть не может гайда, который расскажет тебе, как поступать всегда. Есть принцип единой обязанности, который все понимают по-разному. Ну так вот, не так важно, как ты его понимаешь. Что по твоему входит в термин обязанность, является ли делегирование или посредничество дополнительной обязанностью, можно ли считать валидацию входных данных обязанностью, и так далее. Важно вот что — ты всегда должен об этом думать. Подумал, написал код, подумал ещё раз, написал тесты, подумал ещё раз. Закрыл IDE, пошёл спать и ещё раз подумал. А потом переписал нормально. И так до бесконечности.

    Так работает не только SRP — весь SOLID. Это не штука, которую надо зазубрить для собеседований, и не гайд, по которому надо все делать. Это такая вещь, которая говорит тебе, о чём ты должен подумать, когда пишешь код.

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

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

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

    Если ты владеешь тайным знанием о только что написанной кодовой базе — у тебя есть Git. Клади в свои комиты и пуллреквесты столько информации, сколько хочешь. Чем больше, тем лучше. История гита — это лучшее место для хранения мета-инфы о кодовой базе.

    Правда, есть исключение. Кейс с исключениями (каламбур не специальный). В C# исключения, которые может создать твой метод, не являются частью его типа — единственный механизм, что бы уведомить пользователя твоего кода, что он должен обработать исключение — это xml doc. Лично я пишу документацию об исключениях на уровне интерфейса — потому что считаю, что это не деталь реализации, а часть контракта.

    Да, ещё есть кейс, когда дока публичного апи — часть бизнес-требований, и тут ничего не поделаешь, но пока это не так — к чёрту xml. Пишите C#.

    Я борюсь с многострочностью везде, где могу. Я люблю писать так:

    
    if (somethingBad) throw new Exception();
    if (somethingSuperBad) throw new SuperException();
    if (somethingCriticalBad) throw new CriticalException();
    
    

    Такой код легко прочитать, его легко понять. В отличие от

    
    if (somethingBad)
    {
      throw new Exception();
    }
    
    if (somethingSuperBad)
    {
      throw new SuperException();
    }
    
    if (somethingCriticalBad)
    {
      throw new CriticalException();
    }
    

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

    Да, я против префиксов `I`, `_` и т. д., но я за постфикс `Exception` — потому что это не конструкция языка — это просто наследник определенного класса. Я не могу заставить IDE красить их в отдельный цвет. Базовые классы, интерфейсы, свойства-филды-методы-ивенты-структуры — всё это хорошая IDE умеет подсвечивать.

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

    Если ты имеешь дело с очень длинной строкой, не грех и перенести

    
    var result = DomainName
                  .ClassName
                  .MethodName(
                      parameter1,
                      parameter1,
                      parameter1,
                      parameter1,
                      parameter1
                  )
    
    

    не супер красиво, но тут красиво и не получится. Надо сделать максимально не дерьмово.

    Кстати, есть новая мода писать ',' в конце последней строки любого перечисления. Я её ненавижу, потому что, после запятой должно что-то идти. И когда я вижу такое:

    
    var result = DomainName
                  .ClassName
                  .MethodName(
                      parameter1,
                      parameter1,
                      parameter1,
                      parameter1,
                      parameter1,
                  )
    
    

    У меня начинает болеть мозг.

    Теперь опять к нашему `Api`

    Нам нужно добавить сюда метод, который делает запрос по пути `api/license/install`.

    Я сделал это так:

    
    public interface Api {
      Task<Response.Install> Install(Parameter.Install parameter);
    }
    
    

    Инсталл Инсталл Инсталл? Да.

    `Response.Install`,

    `Api.Install`,

    `Parameter.Install`.

    И мне не нужно придумывать никаких странных имен ака `InstallResponse`, который лежит в неймспейсе `Install`.

    Возникает вопрос с неймингом `parameter`. Я назвал параметр метода словом `parameter`. Попахивает говном, на деле не говно. Потому что это параметр запроса, т. е. слово `parameter` в этом кейсе — сущность предметной области и моего домена.

    Надо сделать класс параметра:

    
    public struct Install {
        public Install(string hardwareId, string userId) {
            this.HardwareId = hardwareId;
            this.UserId = userId;
        }
    
        public string HardwareId { get; }
    
        public string UserId { get; }
    }
    

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

    
    Dictionary<Parameter.Install, Response.Install> sessionCache;
    
    private Response.Install Install(Parameter.Install parameter) {
      if (this.sessionCache.TryGetValue(parameter, out Response.Install response)) {
        return response;
      }
      // иначе делаем запрос на сервер
    }
    
    

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

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

    В дотнете не существует лучшего способа показать, что вот эта сущность — чистые данные, а не сервис, объект с поведением или что-то ещё. Я спроектировал структуру `Parameter.Install` так, что бы она была иммутабельной — как внешне, так и в кишках. Это очень важный момент — когда ты не видишь сценариев мутаций, их следует ограничивать. Во-первых, тоже часть инкапсуляции, во-вторых, работа с иммутабельнымы данными в принципе безопасней и надёжней. В-третьих, в-четвертых, исследования, шмиследования, если коротко — оопшники не правы, фпшники правы. Я, как любой нормальный дотнетчик, разрабатывал с использованием F#, и не готов тратить своё время на доказательства очевидного. F# следующая ступень развития C# не потому, что он новый, а потому, что он более функциональный.

    И вот в чём это проявляется: мой подход со структурами — говно собачье. Потому что в C# структуры имеют конструктор без параметров по умолчанию. Его невозможно сделать приватным или отключить, вся моя надёжность разбивается об этот факт. Этого достаточно, что бы я отказался от структур и переписал параметр так:

    
    public sealed class Install {
        public Install(string hardwareId, string userId) {
            this.HardwareId = hardwareId;
            this.UserId = userId;
        }
    
        public string HardwareId { get; }
    
        public string UserId { get; }
    }
    

    Причём в дотнете есть действительно хороший инструмент чтобы описывать данные — рекорды. Но он есть в F#, а в сишарпе его только собираются добавить. Собираются, потому что он есть в F# и прекрасно работает.

    Сам класс написан на C# 8.0, .net core 3.1, а это значит, что я могу включить новую фичу:

       <Nullable>enable</Nullable>
    

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

    Если коротко, в дотнете древняя и больная проблема с `null`. У тебя все ссылочные переменные могут иметь значение `null`, и компилятор не отличает проверенные от непроверенных. Поэтому в любой кодовой базе, написанной на C#, мы проверяем на `== null` всё и везде. Проверяли. Потому что теперь у нас появилась возможность сказать компилятору — вот эта переменная не может иметь значение `null`. И компилятор будет запрещать присваивать ей `null`. Точнее, он будет кидать варнинги. Для меня это достаточная гарантия. Потому что все проекты делятся на два типа — такие, в которых варнингов нет вообще, и такие, в которых варнингов бесчисленное множество. И на новые никто не обращает внимания. Так вот, для первых, если я включил у себя нулабл и принимаю в конструкторе не нулы, это значит, что они не скормят мне опасные данные. А вот безбожников, которые плюют на варнинги, никакие мои рантайм проверки не спасут, и думать об их удобстве при разработке библиотеки не следует.

    Лично я внутри проекта стал помещать такие штуки

    string? data

    только в модули, которые работают с IO, где они проверяются, превращаются в

    string data

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

    Рассуждая так, я избежал необходимости модифицировать код класса таким образом

    
    public sealed class Install {
        public Install(string hardwareId, string userId) {
            if (hardwareId == null || userId == null) throw new Exception();
    
            this.HardwareId = hardwareId;
            this.UserId = userId;
        }
    
        public string HardwareId { get; }
    
        public string UserId { get; }
    }
    

    У меня ещё актуальна проблема с проверкой строк на `IsEmptyOrWhitespace`, но это уже вопрос бизнес-требований — если такое требование будет, проверку нужно будет добавить.

    У меня C# 8, и у меня есть гарантия, что либой будут пользоваться только проекты на C# 8, иначе я посмотрел бы в сторону атрибутов `[NotNull]`.

    Когда я пишу код не так, как принято в .net-сообществе, мне все время кажется: «Что ты делаешь, идиотина?! Взрослые дяди со всем уже давно разобрались, ты же не думаешь, что ты умнее их?». Как русский человек, я не привык думать, что моё мнение хоть чего-то стоит. В такие моменты мне кажется, что я наваял себе воздушных замков, всё для себя логически объяснил, но всё равно тупой и полностью не прав.

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

    Вот был же случай с вирусом и туалетной бумагой. Люди набивают по три тележки туалетной бумаги. Не поймите неправильно, я не против идеи запастись всем необходимым, но, чувак, три тележки толчанки? Или ты заодно купил себе вагон продуктов, или ты *клинический* идиот. И тут я начинаю думать — ну не может же быть, чтобы они все были идиотами? И правда не может. Зато может быть так, что они все, в каком-то конкретном случае, начинают вести себя как идиоты. Их так много, и это так заразно, что в какой-то моменты все начнут считать идиотом тебя — потому что ты не забил свой дом туалетной бумагой. И вот в таком случае лучшее, что ты можешь сделать, — это посылать их к черту и не поддаваться массовому безумию. Люди, которые передают потенциальные нулы по всей кодовой базе, ведут себя в сто раз более по-идиотски, чем туалетнобумажники.

    У меня уже есть класс `Parameter.Install`. `Response.Install` в целом точно такой же, просто другой набор свойств.

    Пора шагать в сторону реализации. Я зафигачил так:

    
    internal sealed class Api : Public.Api {
        private readonly Rest rest;
    
        internal Api(Rest rest) { this.rest = rest; }
    
        public Task<Response.Install> Install(Parameter.Install parameter) =>
            this.rest.SafePost<Parameter.Install, Dto.Install, Response.Install>(model, Path.Install);
    }
    

    Давайте построчно. Класс запечатанный, потому что я не вижу кейса, где его нужно будет наследовать. Разрешать наследовать на всякий случай я не хочу. Конструктор интёрнал, потому что я хочу отдавать его из либы с помощью какого-нибудь `Create.Api(«endpoint»)`. Это нужно, потому что я не хочу строить у себя полноценный DI, тащить для этого либы и т. д., но при этом мне нужно покрыть либу юнит тестами.

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

    Для этого я сделал свой фасад над либой для запросов — интерфейс `Rest`. Сама либа `RestSharp`. Взял её, просто потому что работал с ней раньше, мне на самом деле это не очень важно — у меня же есть фасад, если че — поменяем. Имплементатор пришлось назвать `RestSharpFacade`. Некрасивый нейминг, но по факту правильный, если помнить, что это фасад, а RestSharp — имя собственное, пусть и дерьмовое.

    У меня есть метод `Install`, который и делает запрос. Ну, идеологически. На практике он делегирует работу моему extension-методу `SafePost`, и это всё, что о нем надо знать.

    А вот `SafePost` придётся рассматривать подробно. Мой интерфейс `Rest` и моя реализация фасада лежат в директории `Http`. Там же лежит файлик `Extension.cs`:

    
    internal static class Extension {
        public static async Task<Response> SafePost<Parameter, Dto, Response>(
            this Rest rest
            Parameter data,
            string path
        ) where Dto : Dto<Response>, new() {
            try {
                var response = await rest.Post<Parameter, Dto>(path, data);
                return response.ToModel();
            }
            catch (System.Exception e) {
              throw new ApiException($"The '{path}' resource request failed",e);
            }
        }
    }
    

    Я выбрал именно метод-расширение, потому что обработка невалидных респонсов — не обязанность класса Api, его работа — описывать и вызывать запросы. И это не обязанность класса `RestSharpFacade`, потому что его работа — упрощать для меня API сторонней либы. Он должен делегировать, а не проверять. Но с точки зрения семантики использования — моему классу `Api` не нужна зависимость, которая занимается проверкой ответа — мне вообще не нужно, что бы класс `Api` знал что-то кроме своих запросов. Метод-расширение подошёл идеально.

    Классический нейминг extension-method в C# — `ClassNameExtensions`. Мне он не нравится. У меня есть директория `Http`, внутри лежит файл `Extension`, в котором лежат все методы-расширения, работающие с сущностями из этого фолдера. И подключаться эти расширения будут вот так: `using Http`. А раз они так будут подключаться — вообще пофигу, как они называются (мы их название нигде не используем) — это раз, а два — для нас не имеет смысла разделять их по сущностям. Ведь подключаем-то мы сразу все расширения из фолдера — по другому не получится.

    Этот метод нужен потому, что кроме `Install` у меня будет ещё десяток запросов, и я не хочу дублировать в них код обработки исключений и конструирования моделей. Метод принимает целых три дженерика — это не круто, но я не смог придумать архитектуру лучше.

    Тут проблема в сериализации/десериализации. Либы в дотнете предлагают такой подход:

    
    class MyIncomingDto {
        [DeserializeAs(Name = "name_in_json")]
        public string? Prop { get; set; }
    }
    
    class MyOutgoingDto {
        [SerializeAs(Name = "name_in_json")]
        public string Prop { get; set; }
    }
    

    Скармливаешь тип `MyDto` либе, и она парсит в него json с сервера. Это удобно, что бы получить данные, но таким классом невозможно пользоваться. Во-первых, класс не может иметь конструктора с параметрами, во-вторых, все поля в нём — nullable, в-третьих, в модели, с которой я хочу работать, кол-во полей и их типы обычно отличаются от исходной. Более того — в моём случае на один и тот же запрос могут приходить разные форматы ответов. И получается, что я могу отдавать клиент-класс с кучей опциональных полей, или, если буду использовать dto+response, смогу выражать это закрытой иерархией наследования. Поэтому мне нужен тип DTO, и тип итоговой модели — `Response`. Который уже пацанский, гарантированно наполненный валидными данными, с такой структурой, которая удобна мне, а не json-у.

    В итоге запрос описывается тремя типами — тип параметра, тип DTO и тип ответа. Так как дизайн RestSharp заточен так, что модели отвечают за то, как они будут сериализованы и десериализованы, я решил, что я так и сделаю. Я выделил интерфейс `ConvertsTo`, который описывает модели, умеющие превращать себя в респонсы.

    Вот так:

    
        internal interface ConvertsTo<out T> {
            T Convert();
        }
    

    все мои DTO его имплементят, и я использую это вот так:

    
       var dto = await this.rest.Post<Parameter, Dto>(Path.Install, parameter);
    
       // Dto is ConvertsTo<Response.Install>, Response.Install - возвращаемый тип.
       return dto.Convert();
    

    Поинт по неймнигу интерфейсов. В дотнете их называют вот по такому паттерну: `ISomethingable`. Про префикс я уже все сказал, постфикс `ble`, по-моему, уместен не всегда. Есть, например, родной интерфейс `IConvertible` — конвертируемый. Название этого интерфейса отвечает на вопросы «какой, что». Мне кажется, это такой костыль, когда суть интерфейса у тебя описывается глаголом, но ты хочешь называть свои сущности существительными. У меня нет сакральной тяги к существительным, поэтому, когда я делаю интерфейс с единственным методом, я называю его глаголом. Получается красиво: `Customer: ConvertsTo` — кастомер, конвертируется в инфу о сущности. У нас есть принцип разделения интерфейсов, и мой нейминг подталкивает к тому, чтобы его соблюдать.

    Вернёмся сюда:

    
    public static async Task<Response> SafePost<Parameter, Dto, Response>(
        this Rest rest
        Parameter data,
        string path
    )
        where Dto : Dto<Response>, new() {
        try {
            var response = await rest.Post<Parameter, Dto>(path, data);
            return response.ToModel();
        }
        catch (System.Exception e) {
          throw new ApiException($"The '{path}' resource request failed",e);
        }
    }
    

    Метод называется `SafePost`, и тут проблема — какой же это сейф, если собака плюется исключениями? Я решил, что раз уж он ловит любое исключение, а отдает одно ожидаемое, то в контексте своего использования (он у меня приватный, и используется внутри одного запечатанного класса) он может считаться безопасным — внутри этого класса, когда вызываешь этот метод, ты не должен обрабатывать ошибки от него, работаешь с ним как с безопасным. Тут стоит поговорить про исключения вообще. Я против исключений идеологичиски. Мне нравится функциональный подход, когда ты используешь `Result`-монаду — вот такую:

    
    // F#
    type Result<T> = value of T | exception of Exception
    
    let sample (data: int Result) =
      match data with
      | value -> value + 1    // если всё норм, вернем data + 1
      | exception ->
            log exception
            0                 // если исключение, логируем и возвращаем 0
    
    

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

    К сожалению, в C# так делать не стоит. Во-первых, в шарпах нет DU — типов-объединениий, и тебе придётся костылить свою `Result`-монаду с помощью наследования. Во-вторых, окей, свой код ты сможешь обрабатывать так. Но сторонний-то плюется исключениями — тебе придется его оборачивать в свою монаду повсюду, что породит кучу некрасивого кода. Не говоря о том, что ты же точно не знаешь, какой сторонний код какие исключения выкидывает — у тебя нет гарантий, только xml doc. Который далеко не факт, что есть и правдив. То есть, оборачивать придётся вообще всё. А, кроме того, надо учитывать — вот я тут для себя всё круто напридумывал, но публичное API моего кода один черт надо делать так, что бы оно было более-менее привычно для среднестатистического дотнетчика.

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

    Это косвенно подтверждается историей с исключениями у джавистов. Создатели Java — умные люди, они сделали обработку исключений обязательной. Но разрабы во всем мире не хотят думать, что делать, если всё идёт не по плану.

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

    
    public static ShouldBeHandled<Response> SafePost<Parameter, Dto, Response>(
        this Rest rest
        Parameter data,
        string path
    ) where Dto : Dto<Response>, new() {
    

    Нейминг женериков. В дотнете их любят именовать T, TSome и т. д. Мне это не нравится. Я допускаю использование имени дженерик-параметра T, когда у тебя T — синоним словосочетания «что угодно». Когда у этого параметра есть хоть какой то смысл, лучше написать его словом. Например `Parameter`. При этом префикс к слову `TParameter` — максимально неуместен. Что ты хочешь им показать? Что речь идёт о типе? Ну, когда сможешь запихать в угловые скобки что-то кроме типов, тогда и приходи со своим префиксом.

    Когда я обсуждал такой нейминг женериков с друганом, он сказал мне: «Я всегда так и делаю». Тогда я подумал, что часть этих подходов я стал использовать в дотнете далеко не первый. Это не плохо. Я очень люблю доходить до чего-нибудь, что известно абсолютно всем, сам — потому что возможность изобретать — пусть даже уже изобретенное — это самое крутое в моей работе. Я не знаю алгоритмов квиксорта и пузырьковой сортировки, хотя мне очень интересно, как они работают. Я могу в любой момент прочитать, как они делаются, но я не читаю — вместо этого я постоянно пытаюсь их придумать. Это кайфовый процесс. Где-то в начале обучения программированию я обрёл навык придумывать код без помощи IDE или блокнота, и теперь, когда я остаюсь наедине с собой, мне всегда есть о чём подумать. Прикол в том, что я не проверяю придуманные алгоритмы сортировки — всегда есть шанс, что я уже придумал пузырьковую, или какую-то ещё. Но мне это не важно. Но если у меня будет задача реализовать сортировку, я загуглю, потому что игрушки игрушками, а работу надо делать хорошо.

    Вообще, мне каждый день приходиться узнавать, как другие люди решают задачи. Профессия разработчика принуждает тебя принять факт, что ты ничего не знаешь, сообщество знает всё. Ты никогда не работаешь один, и ты никогда не работаешь для себя. Когда я работаю над личным пет-проектом, в моей голове есть вымышленая команда из будущего, которая говорит мне: «Серьёзно? Ты думаешь, с этим говном можно будет работать? Загугли, как это должно выглядеть, и ПЕРЕДЕЛЫВАЙ.». Я переделываю. Программисты — линейная пехота, а не горцы с клейморами.

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

    Вы уже знаете, как у меня работает сериализация и десериализация. Осталось прояснить, как устроен `ConvertsTo`.

    
    namespace Dto {
      internal sealed class Install : ConvertsTo<Response.Install> {
          [DeserializeAs(Name = "user_name")]
          public string? UserName { get; set; }
    
          [DeserializeAs(Name = "user_email")]
          public string? UserEmail { get; set; }
    
          public Response.Install Convert() {
              var userName = Guard.AsValidString(this.UserName, nameof(this.UserName));
              var userEmail = Guard.AsValidString(this.UserEmail, nameof(this.UserEmail));
    
              return new Response.Install(userName, userEmail);
          }
      }
    }
    

    `Response.Install` просто содержит два свойства и разрешает их читать. `Dto.Install` умеет быть десериализованым и после этого создавать инстанс респонса. Тут два больших вопроса. Почему Dto отвечает за инстанцирование модели? Попахивает нарушением SRP. И да, это он и есть, если в теле метода `Convert` будет бизнес-логика. Сейчас её нет — есть её делегирование классу `Guard`. То, как сейчас выглядит мой гард, может ввести в заблуждение, потому что он похож на кастомное расширение возможностей языка, но здесь это не так. Условия валидности данных с сервера определяются бизнес-требованиями. Бизнес решает, подходят нам пустые строки, или нет. Эти правила отражены в классе `Guard`. Мой текущий не самый удачный нейминг и сигнатура объясняются просто — у меня ещё нет этих требований. В будущем `AsValidString` заменится на какой-нибудь `AsValidUserName`.

    С учетом всего этого, лучше считать Dto и Response одним целым — просто одна часть (Dto) у нас internal, а другая публичная. В моих глазах это оправдывает конструирование модели здесь.

    Более важный вопрос — использование статического класса `Guard`. Со статическими классами всё плохо. Я делю дотнетчиков на три типа — начинающие, которые пишут статик повсюду, потому что им так легче. Обычные дотнетчики, которые вот прям всё инжектят и слишком умны для статических классов.

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

    
    internal static class Guard {
        /// <exception cref="InconsistentServerModelException" />
        public static string AsValidString(string? value, string propertyName) {
            if (value == null || string.IsNullOrWhiteSpace(value))
                throw new InconsistentServerModelException(propertyName, value);
            return value;
        }
    }
    

    Вот выдержка из моего класса `Guard`. Его работа — проверять значения и швыряться исключениями доменного типа. Из-за исключений функции не чистые, но главное правило соблюдается. Если я захочу покрыть кейсами сценнарий, в котором `Dto.Convert` должен упасть на невалидных данных, мне не понадобится делать мок на `Guard` — гард ведёт себя максимально предсказуемо.

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

    Причём, я бы мог сделать свой класс `Guard` екстеншн-методом к классу `string`. Тут есть интересная магия дотнета, так можно делать и не словить `null reference`. Довольно крутой кейс, но конкретно тут я решил обойтись без методов расширений: я хочу спроектировать гард так, что бы он работал со свойствами dto любого типа, и вся эта логика валидации входных данных была в рамках одной сущности.

    Я каждый день пишу код на C# — я получаю за это деньги, это единственная область, в которой я могу назвать себя профессионалом. С большой натяжкой. Кроме работы у меня есть пет-проекты — я пишу на F#, Java и TypeScript. Из-за того, что я не считаю себя профессионалом в этих языках, я пишу код на них не как принято, а как считаю нужным. И меня не покидает ощущение, что именно так это и должно работать. Да, я не получаю деньги за свой код на TypeScript, но я неплохо его знаю, и, по-моему, мой код на нём получается вполне приемлемым. В отличие от моего кода на C# — я всю жизнь следовал общепринятым практикам, и мой код был похож на говно. С моей точки зрения. Я считал его говном, и всё равно писал так — потому что сообщество умнее меня. Благодаря другим ЯПам, я понял, что можно и по-другому. На F#, например, стайлгайдов особо нет, но мой код понятен, чист и прост.

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

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

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

    Вообще пофигу, как ты переносишь скобки. Как ты именуешь интерфейсы, как ты размещаешь свою бизнес логику, и всё такое прочее. Важно одно — когда ты приносишь мне свой код, я должен быть уверен — ты обо всём этом подумал. Я могу подумать за тебя, но тогда ты не нужен. Твой код кто-то воспринимает как труд и работу не потому, что ты напечатал символы, которые компилируются и решают задачу. Смысл в продуманности. Ты не сделал, как у всех, а сделал так, как здесь должно быть сделано. По-твоему должно. Это ведь ты разработчик, да? Тем более, Фил, ты старший разработчик. Тебя никто бы не позвал поработать, если бы им не нужно было, что бы ты думал при разработке!

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

    Я написал проект по-своему, начал писать статью. В процессе написания пересмотрел ещё пару подходов. Переделал проект с их использованием. Обосрался со структурами — придумал заюзать их, но я некомпетентный болван, который ЗАБЫЛ про дефолтный конструктор. Выпилил структуры. Дописал статью. А потом переделал проект так, что бы он выглядел как у всех.

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

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

    Это не «тупицы» лишние в индустрии. Это я в ней лишний.



    На правах рекламы


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

    VDSina.ru хостинг серверов
    Серверы в Москве и Амстердаме

    Comments 128

      +16
      О, статья фила. Побежал за попкорном, пусть пока комментарии настаиваются
        +8

        Фил, в расте нет наследования, а значит и нет всего класса описаннных тобой проблем.


        Перепиши все на раст и вступай в ряды, пора!

          +1

          Раст шикарен. Как и F#. Но у меня-то работа на сишарпе, куда тут денешься. Хотя нам завозят рекорды, и скоро будут DU, будет получше

            +7

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


            А должны были вырасти и спорить о том как решить любую задачу человечества через map/reduce

              0
              map/filter/reduce же

              P.S. кстати, а как можно медиану вывести через map/filter/reduce?
                0

                filter — частный случай reduce, не? Как и map.


                Сортировку же можем, значит и медиану можем

                  +1
                  filter, реализованный посредством reduce, не сможет корректно работать в некоторых не слишком надуманных частных случаях (функция, передаваемая reduce, должна уметь отличать ситуацию старта reduce, то есть, своего первого вызова от остальных вызовов, не всегда это тривиально, а иногда и вовсе невозможно), так что нет, не частный случай.
                  В P.S. я не спрашивал «можем или не можем», я спрашивал «как»

                  P.S. сообразил, как сделать, чтобы ситуации не надо было различать. Ну ладно, filter — частный случай reduce (но я всё же буду настаивать, что лучше его рассматривать, как самостоятельный инструмент)

                  P.P.S. Вот, кстати, пример, чем плох filter, реализованный через reduce: для корректной реализации потребуется создать копию исходного кортежа с добавленным пустым кортежем в начало. Это может быть совсем невозможно, если у нас настолько большой кортеж, что занимает большую часть памяти (у «чистого» filter такой проблемы нет, у него может быть проблема только с размещением результата, а тот может быть существенно компактнее)

                  P.P.P.S. Ну и вдогонку: почему бы тогда и про map не «забывать», если и она частный случай reduce?
                    0
                    Замечание к P.P.S.: если есть что-то, что может «на лету» выдавать нечто, удобоваримое для reduce, добавив пустой кортеж перед первым элементом, тогда filter может быть реализован через reduce без «неприятных побочных эффектов», описанных мной
                      +1

                      Не выдержал, скажу все таки. Зачем нужно отличать первый элемент от остальных, не понимаю не только я, но и все авторы всех имплементаций filter во всех языках. (Вот, ниже в псевдокоде).


                      filter(enum, fun) = reduce(enum, new typeof(enum), fn e, acc ->
                        if fun(e) then append(acc, e) else acc
                      end)
                        0

                        Можно и без псевдокода.


                        filter' :: (a -> Bool) -> [a] -> [a]
                        filter' f = foldr (\elt acc -> if f elt then elt : acc else acc) []

                        работает как надо (можно даже доказать, но не в хаскеле).

                          0

                          Тогда уж


                          filter' : (fn : a -> Bool) -> List a -> List a
                          filter' f = foldr (\elt acc => if f elt then elt :: acc else acc) Nil

                          Вот теперь доказывайте :)

                          0
                          Эта штука будет работать, если кортеж состоит из кортежей, или запихнёт всё в копию первого элемента?
                            +1

                            Я не понял вопрос, извините. Есть Foldable / Enumerable. Кортеж это, список, или массив — дело сто тридцать пятое.


                            Если на нем определен fold / reduce — то filter можно определить как показали выше (псевдоязык, haskell, idris соответственно).


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

                +4

                В шарпе есть FFI и весь новый код после 1 января 2021 должен быть написан на расте в соответствии с сегодняшним Указом Путина «О сокращении выбросов парниковых газов».


                How we integrate Rust with C#

                  +2

                  Так найди работу на расте и все будет ровно так как ты хочешь.

                    +1

                    Как_нарисовать_сову_найти_работу_на_расте.jpg

                      0

                      Легче, чем на идрисе.

                +18
                Согласен. Автор гавно.
                  +29
                  Я не хочу иметь название `HttpClient.License.Public.LicenseApi` — два слова `License` подряд — что хотел сказать автор? Сейчас доступ к интерфейсу будет выглядеть так: `HttpClient.License.Public.Api`. Кристально чисто.
                  А надо всё таки называть LicenseApi. И причина не в чистоте кода, а в том что когда у вас будет большой проект с несколькими языками, документацией и т.п. и кто-то решить поискать во всем объеме этой информации по имени класса Api он вас проклянёт, а по LicenseApi найденых вхождений будет на пару порядков меньше.

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

                    Надо, да. К сожалению.

                      +3
                      Подумайте над идеями, как это исправить «правильными» системами поиска по исходному коду. Которые завоюют мир, а вы, их автор, будете сами себе платить зарплату. Шутка. Я вижу (поверхностно, конечно) ваш психологический профиль, это не ваш метод. Ваш метод — сбросить пар на хабре, продолжить комфортно получать зарплату, где получаете. Я то же так делаю, но не сбрасываю пар на хабре.
                      +3
                      И причина не в чистоте кода, а в том что когда у вас будет большой проект с несколькими языками, документацией и т.п. и кто-то решить поискать во всем объеме этой информации по имени класса Api он вас проклянёт, а по LicenseApi найденых вхождений будет на пару порядков меньше.

                      Мне кажется, или проблема вовсе не в нейминге, а в убогих средствах для поиска?

                        +4

                        Некоторые люди любят, когда с кодовой базой можно работать не только через IDE, но и через grep, sed и nano не обвешанный плагинами vim.

                      +3
                      Статья для дотнетчиков, так что, парни, давайте передохнём минуту и хорошенько поржём над «НАСТРОИТЬ ДЕБАГ».

                      Да, давайте поржем над Филом и пятью его загадочными сеньорами-фронтами, которые не смогли прочитать документацию электрона.
                      Когда все необходимые шаги прямо таки разжеваны в первых же ссылках гугла, и вообще сами по себе очень стандартные (запустить процесс с соответствующими дебаг-командами, подключить remote debugger) — тут конечно же без пяти сеньоров не обойтись.
                      Или у вас такая IDE, что не умеет в это всё, как умеет та же VSCode? Ну так проблема на вашей стороне, надо было брать инструменты, которые умеют.
                        0

                        Я нашел несколько рецептов для VS и WebStorm. Интересно, это пробовали и не подошло (ну мало ли какие там особенности). Или сеньоры специфические.


                        https://stackoverflow.com/questions/46500302/attach-visual-studio-debugger-to-electron-app
                        https://blog.jetbrains.com/webstorm/2016/05/getting-started-with-electron-in-webstorm/

                          +1
                          Я однажды (поскольку с электроном меня жизнь не сильно-то сводила) для одного хорошего человека взялся подебажить — поставил электрон, скачал его код, дописал включение дебага, подцепился из VSCode, в котором я всегда пишу — всё завелось с полпинка.

                          Оно вот правда всё работает на уровне «прочти и сделай как написано», по крайней мере в VSCode. Вернее, работало еще в 2018.
                        +8
                        Кстати, есть новая мода писать ',' в конце последней строки любого перечисления. Я её ненавижу, потому что, после запятой должно что-то идти.

                        MethodName(
                        parameter1,
                        parameter1,
                        parameter1,
                        parameter1,
                        parameter1,
                        )
                        Конкретно в параметрах метода так делать нельзя, в вашем примере будет ошибка компиляции.

                        Ну и сделано это для удобства использования при автогенерации кода, ненужны специальные подпрыгивания, чтобы отслеживать когда надо её ставить, а когда нет.
                          +6
                          Это косвенно подтверждается историей с исключениями у джавистов. Создатели Java — умные люди, они сделали обработку исключений обязательной. Но разрабы во всем мире не хотят думать, что делать, если всё идёт не по плану.
                          Многие из Java мира имеют альтернативное мнение и называют это
                          Checked exceptions — Java’s biggest mistake
                            +4

                            Это оттого, что в джаве полиморфизма по этим экзепшонам нет. А так-то они по духу похожи на хаскелевскую монаду Either и ей подобные, а это, ИМХО, самый удобный способ обработки ошибок.

                              +2
                              Многие из Java мира имеют альтернативное мнение и называют это
                              Checked exceptions — Java’s biggest mistake

                              Я тоже так думал в Java, пока не пописал год на C#. После этого изменил мнение на противоположное :)

                              +22

                              У этой многострадальной запятой есть ещё одно назначение: при добавлении элемента в список мы не меняем предыдущую строку.
                              И легче смотреть историю изменений в git.

                                +8
                                Есть альтернативный вариант, но он не для слабонервных:
                                SomeList(element_1
                                       , element_2
                                       , element_3
                                )
                                  0

                                  Ага, работал с таким code style.


                                  Но, в большинстве случаев — это вкусовщина, поэтому я не испытываю эмоций на сей счет.

                                    +2

                                    Отчего ж не для слабонервных? Исключительно вопрос привычки.


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


                                    data Foo = FooCtor
                                      { field1 :: String
                                      , field2 :: Int
                                      , ...
                                      }

                                    как пример.

                                      +2
                                      Что ты такое?(
                                        0
                                        SQL Management Studio стандартные запросы в таком виде генерирует:
                                        /****** Script for SelectTopNRows command from SSMS  ******/
                                        SELECT TOP (1000) [Id]
                                              ,[Field1]
                                              ,[Field2]
                                          FROM [DataBase1].[dbo].[Table1]
                                        +4

                                        А если надо новый первый элемент вставить, то опять две строки менять.

                                          0
                                          Тогда пустой список аргументов/элементов придётся везде определять как
                                          f(,
                                          )
                                    0
                                    К сожалению, далеко не всем интересно изучать новые подходы и придумывать свои велосипеды. Они научились клепать как в гайде и отклонение от него — это нагрузка на мозги, к тому же нежелательная. Я вот, к примеру, недавно вычитал про такую штуку, как аспектно — ориентированное программирование. Вдохновился как круто можно будет в проекте все залогировать (пишешь код для логов в одном месте и все работает). Логов у нас великое множесто, подумал убедить всех внедрить… А потом подумал, что я услышу что-то вроде «ну хочешь — сделай», придется самому искать время на внедрение, а потом убедить всех коллег, что это круто. А потом придет товарищ и сделает как обычно, потому что он и не слушал, что я предлагал, и вообще ему бы быстрее задачу закрыть. И отбросил идею.
                                    Вот так и выходит, что приходится равняться на стандарты тех, кто учиться не хочет и не хочет пробовать новые идеи.
                                      +4

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

                                        0
                                        Недавно была хорошая статья прямо не касающаяся конкретно программирования, но там есть очень правильная идея «рычага».

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

                                        Я сильно надеюсь что в конце концов спецификации языка и IDE будут включать подобную поддержку из коробки, ну а пока каждый д… т как он хочет.
                                          0
                                          Нет оснований полагать, что макросы, генерация исходников или AOP значительно ускоряют разработку всегда и везде или хотя бы часто и в широком спектре задач. На поверхности все прекрасно, особенно с макросами. Все эти инструменты (а также ООП, ФП и т.д.) придуманы для решения проблемы сложности. Эти инструменты не адресуют проблему сложности напрямую, они дают «язык», на котором можно о проблеме говорить. И тут загвоздка — мы не знаем как понять насколько «язык» адекватен проблеме, кажется мы даже не знаем как понятие «адекватен проблеме» определить.
                                            0

                                            Мы даже не знаем проблему определить, кроме как "софт — это сложно"

                                              0
                                              Я регулярно думаю о частной проблеме — человеческий, но формализованный язык для общения с самим собой (ладно с ним, с остальным человечеством). Переносимость идей, алгоритмов через моды / синтакс разных ЯП / изменения в синтаксе даже одного ЯП (мой ЯП — C++, он стал динамичнее меняться в последние лет 15). Идеи AOP, DSL мне лично нравятся и для меня лично они адекватны проблеме. Далее, я — довольно типичный «опытный программист со стажем», адекватность для меня что-то значит и для остального человечества. Это я просто в антитезу вашему (понятному мне) скепсису.
                                          +1
                                          del
                                            +2

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


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


                                            Некоторым даже за это платят, всякие Technology Advocate.

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

                                              +1
                                              Прошу прощения, что удалил комент на который вы ответили.
                                            0
                                            Недавно тоже захотел использовать AOP и подтянуть какой-то weaver. Так вот, проблема с либами таки, их живых — fody да postsharp. При чем у первого половина плагинов мертвы автор пропагандирует какую-то странную идеологию «добровольно принудительной оплаты», хотя лицензия MIT, а второй дороговато стоит (комюнити вресия уровня «ну вот это у тебя проработает только неделю после билда» SIC). В итоге таки остановился на фоди, но юзаю пока по миниммуму, только для нулчеков. Потом посмотрим, есть идеи.
                                            +11

                                            TL;DR: я выстрадал и выкатил 100500 новых правильных практик написания кода: нейминга, форматирования, наследования, инкапсуляции и прочего, но мой код можно понять только изнутри той IDE, которая нравится мне.

                                              +2

                                              Всё так. Только в .net все пишут или в райдере, или в студии с решарпером, который превращает её в райдер. Короче, ide то у всех одна и та же.

                                                +1

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


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


                                                Так что я все-таки убежден, что код обязан не терять в читаемости безо всяких там IDE.

                                                  +3

                                                  Я бы подискутировал, на самом деле. Хороший код всегда красивый, красивый код не всегда хороший. Но эти вещи связаны. Эстетическая красота кода — это в том числе и красота архитектуры и дизайна — качества именно хорошего кода.


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

                                                    +1
                                                    Хороший код всегда красивый, красивый код не всегда хороший.

                                                    Отнюдь. Иногда «годный» код от «хорошего» — отличают бенчмарки, и приходится драться за каждую миллисекунду. И вот тут-то красотой приходится жертвовать. Пример из совсем недавнего: писал я там одну библиотеку, и очень красиво хранил внутреннее состояние в AVL Tree. А потом невзначай выяснилось, что удалений в моем случае практически нет, добавления обычно можно свести к конкатенации, а вот поиск прям частый. И я переписал все на связных списках с костылями, дискретным доступом по типу хэшмапа (O(1)) и кодогенерацией matching clauses для частых паттернов. И выиграл в производительности в 10 раз.


                                                    Стал ли код менее красивым? — Несомненно. И понять его теперь в двадцать раз сложнее.


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

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




                                                    Или вот исключения и ивенты. Что такое ивент? А в языке с нативной моделью акторов? А в джаваскрипте? А в руби? Синий или зеленый? Курсив или болд? А так-то, для общего понимания кода, мне это все не важно: детали реализации. Мне важно, что автор этого кода считает сие ивентом, что я пойму по ключевому слову Event, а по раскраске не пойму. И если в коде throw new NotifyEvent();, то тут что-то не так, а если throw new Notification(), и Notification красненькое и жирненькое — то я хз.


                                                    В общем, с тезисом про то, что код должен доставлять эстетическое наслаждение — я согласен чуть более, чем полностью (с оговорками, конечно, но в общем случае — так). А с тем, что без IDE мне потребуется на понимание в пять раз больше времени, и это нормально — категорически нет.

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

                                                      Есть такая концепция как intelligence amplification — в самом простом случае можно сравнить эффективность решения математических задач в уме vs человеком у которого есть листок бумаги и ручка. Вполне допускаю что существуют те кто в уме будут более эффективно решать, но в целом бумага плюс ручка дают существенную фору.

                                                      Также и IDE, правильная IDE является очень эффективным усилителем как качества так и производительности _обычного_ программиста. Даже плагины к ней могут играть существенную роль (VisualStudio vs VisualStudio+ReSharper).
                                                        +4

                                                        Ээээ… Спасибо на добром слове.


                                                        эффективность решения математических задач в уме vs человеком у которого есть листок бумаги и ручка

                                                        Не-не-не. Писать код каждый волен в чем ему удобнее — тут вообще никаких вопросов. Я докопался до «не надо заморачиваться самодукентированным кодом, потому что мне IDE все подсветит», и возражаю с точки зрения читающих код.


                                                        Код же читают больше и лучше, чем пишут, так вроде кто-то из великих демагогов сказал?


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


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


                                                        Простой пример: хаскель вон типизирован не в пример лучше шарпа, что как бы должно помогать в IDE найти нужную функцию прям по сигнатуре. Но читать хаскель невозможно, если ты не пишешь на нем 24/7 — из-за дикой мешанины бесконечного количества функций в Prelude и стандартных импортах. Тут так же: вся красота, описанная в статье — write-only. Читателю будет легче, если у интерфейса будет префикс. Точка.

                                                          0
                                                          Согласен по поводу чтения, но надо вероятность возникновения такого события учитывать, неудобство человека который раз в полгода что-то посмотрит, с учётом того что он и так язык не знает и ему придётся детально разбираться, может не стоить выгод приобретаемой основной командой.

                                                          И вот тут-то и окажется, что документация должна быть first class citizen
                                                          В теории да, но на как говорят
                                                          В теории, разницы между теорией и практикой не должно быть. Но на практике ...
                                                          Есть объектвиная реальность данная нам в ощущения и документация там «illegal immigrant» по вашей терминологии.
                                                          Читателю будет легче, если у интерфейса будет префикс. Точка.
                                                          Моя точка зрения, что надо следовать принятым практикам, чтобы завтра пришёл новый боец и с достаточной вероятностью он уже знает что, как и где. Для c# это значит префикс «I» перед интерфейсом и префикс «T» перед дженериками. Даже если лично моё чувство прекрасного страдает.

                                                          Если же в хаскеле не принято ставить префиксы (не знаю так это или нет), то лучше не надо, даже если сильно хочется.
                                                            +1
                                                            Если же в хаскеле не принято ставить префиксы (не знаю так это или нет), то лучше не надо, даже если сильно хочется.

                                                            Тут дело в том, что префиксы уродливо выглядят с операторами. Сравните arr ! idx с arr A.! idx или f $> v с f F.$> v.


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

                                                              0
                                                              Стиль, с которым я согласен больше всего (но ленюсь следовать)
                                                              А IDE (если вы пользуетесь idea + intellij-haskell) не позволяет заставлять вас следовать определённому стилю или по крайней мере помогать в приведении к выбранному формату.
                                                                0

                                                                «Ленюсь» — не совсем точное слово. Это не только лень, но и некоторое личное эстетическое неудовольствие. Не знаю, почему, но все эти огромные явные списки импортов расстраивают.

                                                              +1
                                                              неудобство человека который раз в полгода что-то посмотрит, с учётом того что он и так язык не знает и ему придётся детально разбираться, может не стоить выгод приобретаемой основной командой

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

                                                              документация там «illegal immigrant»

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


                                                              Для однопоточного перекладывания джейсона это, наверное, оверхед, впрочем.


                                                              надо следовать принятым практикам

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


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

                                                              +1
                                                              Но им может потребоваться узнать, как там оно реализовано, для каких-то своих нужд.

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

                                                                +2
                                                                Простой пример: хаскель вон типизирован не в пример лучше шарпа, что как бы должно помогать в IDE найти нужную функцию прям по сигнатуре. Но читать хаскель невозможно, если ты не пишешь на нем 24/7 — из-за дикой мешанины бесконечного количества функций в Prelude и стандартных импортах.

                                                                Да нормально всё, на самом деле. Я тут последние полгода-год пользуюсь idea + intellij-haskell, и оно прям рулит и педалит с точки зрения навигации по коду, включая прыжки в библиотеки.


                                                                А вот почему в идрисе или агде нет какого-нибудь хуглоподобного поиска типа «дай мне функции с типом ∀ m n p q → m ≤ n → p ≤ q → m * p ≤ n * q», я не понимаю, и без них очень печально, особенно когда в стандартной библиотеке наворачивают слои абстракций, и ненормализованные типы начинают выглядеть не как такие вот утверждения, а как какое-нибудь _*_ Preserves₂ _≤_ ⟶ _≤_ ⟶ _≤_. В коке, что интересно, такая ерунда есть. Но это, в любом случае, уже совсем другая история.

                                                                  0
                                                                  Я тут последние полгода-год пользуюсь idea + intellij-haskell, и оно прям рулит и педалит с точки зрения навигации по коду, включая прыжки в библиотеки.

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


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


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


                                                                  почему в идрисе или агде нет какого-нибудь хуглоподобного поиска

                                                                  Потому что не хватает рук? Я близок к тому, чтобы самому этим заняться, как только второй компилятор хоть чуточку стабилизируется и превратится в полноценный LS, как первый — нырну.

                                                              0

                                                              Да, согласен. Моё утверждение, что хороший код всегда красивый — неверное. Читабельный и поддерживаемый код всегда красивый. А в случае с перфомансом — часто есть трейдоф между читабельностью и производительностью, и если код критичен к скорости, то он может быть не красивым и при этом хорошим.


                                                              Другой вопрос, что критичность по перфомансу — редкость. а вот читать и поддерживать надо всё.

                                                              0
                                                              Одно из самых красивых решений — код для быстрого вычисления обратного квадратного корня. Это, наверное, самый легендарный хак из всех.
                                                              Но код «красивым» назвать язык не повернется. Это сплошной WTF.
                                                              Впрочем, времена хакеров ушли, пришли времена нытиков.
                                                        +1
                                                        Разобрались с префиксами, теперь нам режут глаза фигурные скобки.

                                                        Ага, щаз! Я встречал вот такой гибрид:


                                                        internal static class SomeClass {
                                                        
                                                            public static void DoSomething(bool arg1, bool arg2) {
                                                        
                                                                if (arg1) {
                                                        
                                                                    if (arg2) {
                                                        
                                                                        some_precious_code ...;
                                                                        some_precious_code ...;
                                                                        some_precious_code ...;
                                                                        some_precious_code ...;
                                                                    }
                                                                    else {
                                                        
                                                                        some_more_precious_code ...;
                                                                        some_more_precious_code ...;
                                                                        some_more_precious_code ...;
                                                                        some_more_precious_code ...;
                                                                    }
                                                                }
                                                                else {
                                                        
                                                                    some_more_precious_code ...;
                                                                    some_more_precious_code ...;
                                                                    some_more_precious_code ...;
                                                                    some_more_precious_code ...;
                                                                }
                                                            }
                                                        }

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

                                                          –5

                                                          Тех, кто экономит пробелы, нужно приговорить к пожизненному расстрелу!


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

                                                            +2
                                                            Это ж как потом читать что тут где во что вложено?!

                                                            Отступы же, которые делаются согласно кодстайлу.

                                                              –2

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


                                                              В таком виде как-то ни туда, ни сюда…

                                                                +2

                                                                Эти "нравится" и "не нравится" — вопрос привычке. Вон в Джаве так все пишут.

                                                                  +1

                                                                  Согласен. Я и не такое вижу периодически, но моих страданий от этого прибавляется, а потому сам обычно переношу открывающую скобку на новую строку. В отличие от автора поста я воспринимаю это так:
                                                                  <Заголовок> Mega_function </Заголовок>
                                                                  { </отступ от заголовка>
                                                                  <Полезный код>
                                                                  a=a+b
                                                                  </Полезный код>
                                                                  } </Отступ перед новым "параграфом" кода>


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

                                                                    0

                                                                    Ну вообще, я ниже написал, что мой выбор, чтобы все писали по стайл гайду принятом в данной экосистеме. Если C# — то скобки на отдельной строке. И желательно проверять все стайлкопом.


                                                                    А в другой экосистеме я буду по другому. Вот в питоне и F# вообще только отступы.

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

                                                            У каждого блока кода получается как бы «смысловой заголовок» с выступом влево и небольшой отступ перед следующим блоком, что визуально его оформляет в отдельную сущность. В том же варианте, который Вы показали, if'ы, объявления и их содержимое(!) мною при беглом чтении воспринимаются как отдельные «смысловые блоки», что несколько затрудняет и замедляет работу.

                                                            Кстати, специально для таких обсуждений у меня есть такой вариант стиля. И скобки на одних столбцах, и нет лишних вертикальных пробелов. Лепота!
                                                            public static void DoSomething(bool arg1, bool arg2)
                                                            {   if (arg1)
                                                                {   if (arg2)
                                                                    {   some_precious_code ...;
                                                                        some_precious_code ...;
                                                                        some_precious_code ...;
                                                                        some_precious_code ...;
                                                                    }
                                                                    else
                                                                    {   some_more_precious_code ...;
                                                                        some_more_precious_code ...;
                                                                        some_more_precious_code ...;
                                                                        some_more_precious_code ...;
                                                                    }
                                                                }
                                                                else
                                                                {   some_more_precious_code ...;
                                                                    some_more_precious_code ...;
                                                                    some_more_precious_code ...;
                                                                    some_more_precious_code ...;
                                                                }
                                                            }
                                                            

                                                              0
                                                              В том же варианте, который Вы показали, if'ы, объявления и их содержимое(!) мною при беглом чтении воспринимаются как отдельные «смысловые блоки», что несколько затрудняет и замедляет работу.

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


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

                                                            +3
                                                            В основном согласен, но не с этим
                                                            Кстати, есть новая мода писать ',' в конце последней строки любого перечисления. Я её ненавижу, потому что, после запятой должно что-то идти

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

                                                              А чем не угодила запятая в конце списка?
                                                              Это в русском языке после запятой должно что-то быть, а в данном случае это способ "перебздеть", чтоб однозначно было понятно, что имя параметра/переменной/значение элемента массива указано верно и окончательно. Я бы, конечно, какой-нибудь октоторп использовал вместо запятой, но создатели языков программирования решили иначе, а потому имеем что имеем…

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

                                                                Энное количество лет назад я работал под началом прекраснейшего программиста (и посредственнейшего начальника, но да это не имеет тут значения) — этот человек, будучи отягощен высокой должностью и бесконечными совещаниями с вовлечением в процесс управления конторой, иногда просто находил откуда-то время написать немного кода, и решить задачу, которую банально не осиливал решить никто другой. Скажем, за четыре дня выходных запилить наколенный прототип движка MS AS, который мог в агрегацию и представление данных, подключался к базам и потоковым данным, парсил и компилировал MDX (не весь конечно, а самый-самый минимум в виде селекта по многомерным данным и полудюжины самых нужных для прототипирования функций), имел минимально возможную цикломатическую сложность, не жрал лишней памяти, и, главное, имел архитектуру, которая ни разу (пока я еще работал в той конторе, но это много лет) более не переделывалась, так как расширялась везде, где нужно.

                                                                Код, который он при этом производил — был, в общем-то абсолютно никакущий. По любым стандартам, даже самым мягким. Там были баги. Там было множество неотработанных edge case. Естественно, там не было Идеологически Верного Нейминга и Форматирования. Тесты там существовали в виде экспериментальных прогонов этого кода в main(). После него, на этот код надо было напустить еще парочку более тупых программистов (типа меня, в те времена я еще немного в Яву мог), которые бы пришли, навели порядок, пофиксили баги, написали бы правильные имена, тесты, и всё вот это. Разница, однако, была в том, что он за четыре дня писал в целом рабочий код, который мы написать за хоть сколько-нибудь обозримое время просто не могли (за бесконечное время конечно бы написали бы, но у бизнеса столько нет) — и этот код приносил неслабые деньги, его, после прилизывания, можно было допиливать до MVP и показывать серьезным дядькам в галстуках и продавать еще не написанные продукты с его использованием за огромные деньги.

                                                                Так вот, полезность такого «говнокода» (в кавычках) я не просто мог видеть, но и собственными глазами видел, в форме циферок в долларах с большим количеством нулей справа, отображающихся в публичной финотчётности конторы, спустя квартал после того, как мы это всё облизали и собрали демки.
                                                                А вот полезность идеологически вылизанного перекладывателя жсона (или http-запросов, что совсем недалеко ушло) — она мне представляется прямо таки на порядки более сомнительной. Соответственно, оформительские многочасовые страдания над таким кодом — они недалеко ушли от многочасовых оформительских страданий над кодом местячкового локального пет-проекта для конкретной узкой задачи, который заведомо никому не нужен и вообще пишется write once кодом.

                                                                Прикольное, но на практике совершенно бесполезное извращение.
                                                                  0
                                                                  Ну все-таки прототип и каркас это разные вещи. Прототип пилится чтобы быстро понять, возможно ли оно в принципе. Автор же скорее говорит про каркас будущей системы, в которой вроде как уже прототипировать не надо, а надо сделать так, чтобы было красиво -> понятно. Хотя и рассмотрено это на небольшой задаче, поэтому и кажется что тут и заморачиваться незачем
                                                                    0
                                                                    Это вообще сугубо неважный вопрос. Важный — зачем оно всё надо, и для чего будет использоваться. И какие деньги принесет.
                                                                    Если вам надо переложить пять хттп-запросов — многочасовые страдания над каркасом идеального перекладывателя запросов бесполезны.
                                                                      +3

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


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

                                                                        +3
                                                                        И единственный способ это сделать — отнаследоваться от класса и прокинуть его в паблик

                                                                        В народе этот антипаттерн известен как "Паблик Морозов" :)

                                                                    +5
                                                                    Кстати, есть новая мода писать ',' в конце последней строки любого перечисления.

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

                                                                      –4
                                                                        +4
                                                                        Ощущение, что автор статьи не работает в команде.
                                                                        Да и в целом — код без комментов и «лишних» скобок? Поддержкой видимо тоже не занимается.
                                                                        • UFO just landed and posted this here
                                                                            +2

                                                                            Про Питон Eго Величество в целом написал тут

                                                                              –3

                                                                              Я слышал, что у них есть общепринятый инструмент для стат типизации, нет?

                                                                                +1

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

                                                                                  0

                                                                                  У него с выразительной силой так себе.

                                                                                    +1
                                                                                    Да, есть тайпчекер, mypy. Норм, но похуже тайпскрипта.
                                                                                  • UFO just landed and posted this here
                                                                                    –1
                                                                                    Поддерживаю — Python, Django+++++++
                                                                                    +5

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


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


                                                                                    Префиксы и суффиксы теоретически зло, но я буду писать как принято в данной экосистеме, чтобы быть понятным. Ибо зло это небольшое. Суффикс Exception тоже нехорош, так как по названию класса должно быть понятно, что это ошибка (Например InvalidArgument).


                                                                                    Они и должны называться одинаково, это одинаковые по смыслу сущности

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


                                                                                    Название API кажется странным. Для внешнего потребителя все API. Например строка по такой логике тоже должна называться API. String.API.


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

                                                                                      –4

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


                                                                                      Название API — и правда странное, моя ошибка. Должно быть — WebApi, или %Domain%WebApi

                                                                                        +1

                                                                                        тогда мне непонятно, почему это интерфейс, а не неймспейс, и как он может быть у Http клиента менеджер лицензий или это не HttpClient а RestClient? Тогда слово Web должно быть уже внутри слова HTTP.


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


                                                                                        using KingOfDevs.SpecificRest.WebApi;
                                                                                        
                                                                                        // если по-вашему а не по стандарту
                                                                                        License.Repository licenses = GetSpecificRestClient().Licenses;
                                                                                        
                                                                                        licenses.Remove(myLicense);

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

                                                                                      0
                                                                                      дел
                                                                                        +2

                                                                                        Можно тоже в холиваре поучаствую? :)


                                                                                        Лично я люблю префиксы для полей классов, потому что они


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

                                                                                        Но вообще, как пишущий на С++, скажу одно — иметь единое соглашение об именах для всего языка (как в C#, например) — это великолепно, вы просто не понимаете своего счастья! Пусть лучше будет неидеально, но зато единообразно.

                                                                                          +1
                                                                                          А почему в C++ не принято писать `this.` как в C# вместо префиксов полей?
                                                                                            –1
                                                                                            Гипотеза: дело в том, что первые пользователи C++ были C программисты, экономили байты исходного кода (кроме того, замечательная более поздняя идея делать this. (this-> на C++, мне лично сама -> противна из-за nullptr dereferencing) была придумана более поздно, в языке Java. Так сложилось, короче. C++ более старый, логичнее задуматься о том, почему и чем идея с this. (Java, C#), увеличивающая многословность, количество ударов пальцами по клавиатуре, правильная и полезная. Я не подвергаю эту идею сомнению, хорошая идея, но не универсальная. Есть другие идеи, точки зрения, инструменты проверки соответствия кода оным.
                                                                                              0
                                                                                              Вопрос был скорее к Amomum про конкретно его стиль. Просто this-> решает обе его задачи.

                                                                                              Кстати, в F# this можно именовать как угодно:
                                                                                              member me.Prop with get() = me.GetPropValInternal()
                                                                                                +2

                                                                                                Мне this нравится тем, что во всяких парных методах (compare, equals, etc.) можно называть аргумент that и обращаться к полям как this.field, that.field.

                                                                                                0

                                                                                                Я пробовал так делать — и это просто дольше, длиннее и менее удобно. Особенно -> ставить (даже если IDE автозаменяет точку на стрелку, она при этом немножко подтупливает).


                                                                                                Это, разумеется, мои личные ощущения — но и весь пост о личных ощущениях. Почему в целом по языку не принято — судить не берусь, но в С++ вообще туго с единым стилем; может где-то и принято.

                                                                                                  0

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

                                                                                                    0
                                                                                                    Есть же линтеры всякие. В случае с шарпом .editorconfig имеет правило для того чтобы подставлять this или подчёрк где нужно и может сломать билд при надобности.
                                                                                                  0
                                                                                                  • Они помогают избегать дурацких проблем с придумыванием имен для параметров в конструкторах или геттерах.

                                                                                                  Проблемы языка, не? В других подобных проблем просто нет:


                                                                                                  struct Dto {
                                                                                                      id: u32,
                                                                                                      name: String,
                                                                                                  }
                                                                                                  
                                                                                                  impl Dto {
                                                                                                      fn new(id: u32, name: String) -> Self {
                                                                                                          Self { id, name }
                                                                                                      }
                                                                                                      fn name(&self) -> &str {
                                                                                                          &self.name
                                                                                                      }
                                                                                                  }
                                                                                                    0

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

                                                                                                      0

                                                                                                      Я бы не сказал, что этот пример демонстрирует преимущества раста в экономии на именах переменных. В этом примере как раз показывается, что Rust вначале создаёт сам себе проблему с boilerplate code (тк требует в конструкторе в общем случае указывать имена полей), отсутствующую в C и плюсах:


                                                                                                      impl Dto {
                                                                                                          fn new(_id: u32, _name: String) -> Self {
                                                                                                              Self { id: _id, name: _name }
                                                                                                          }
                                                                                                      }

                                                                                                      И потом героически её решает с использованием сахара под названием field init shorthand, который и применяется в вашем куске кода. 1 — 1 = 0.


                                                                                                      Сишный код не требует никаких имён полей:


                                                                                                      typedef struct Dto {
                                                                                                          unsigned int id;
                                                                                                          const char   *name;
                                                                                                      } Dto;
                                                                                                      
                                                                                                      const Dto make_dto(unsigned int id, const char *name)
                                                                                                      {
                                                                                                          Dto ret = {1, "abc"};
                                                                                                          return ret;
                                                                                                      }

                                                                                                      Если уж демонстрировать безымянные конструкции раста, то на tuple struct:


                                                                                                      struct Dto(u32, String);
                                                                                                      
                                                                                                      fn main() {
                                                                                                          let foo = Dto(1, "abc".to_string());
                                                                                                          println!("{}", foo.1);
                                                                                                      }
                                                                                                        +2

                                                                                                        В C/C++ подобный способ инициализации (кстати, как он называется) чреват отстрелом ног — порядок можно перепутать, если типы позволяют:


                                                                                                        typedef struct Dto {
                                                                                                            int foo;
                                                                                                            int bar;
                                                                                                        }
                                                                                                        const Dto make_dto(int bar, int foo)
                                                                                                        {
                                                                                                            Dto ret = {bar, foo};
                                                                                                            return ret;
                                                                                                        }

                                                                                                        Либо есть designated initializers (C99, C++20):


                                                                                                        {.foo=123, .bar=456}

                                                                                                        которые возвращают проблему дублирования, о которой вы говорите. Так что, ИМХО, в Rust сделано неплохо.

                                                                                                          0
                                                                                                          Такой способ инициализации возможен и в расте, только еще имена полей надо указывать, порядок на них не задан. Но это работает только до тех пор, пока ваш тип — это DTO, иначе приходится делать поля приватными и вводить конструкторы. И ваш пример какой-то странный, в make_dto аргументы не используются.
                                                                                                      +1
                                                                                                      Удивительно, как много созидательной энергии, увы, тратится на форматирование, соглашения о наименованиях, такие мелкие штуки, которые Компьютер должен делать (и делает) проще для мыслителей-программистов. Однако, у мыслителей-программистов есть некоторая проблема с приоретизацией, реальная проблема — человеческий мозг хватается за мелкие штуки первым делом, ему проще выполнить рутинную работу по переформатированию, переименованию, расставлению скобочек и запятых красивенько так — и это на подсознательном уровне. При том, что Компьютер сделает всё это ОК за миллисекунды.

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

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

                                                                                                      Пример реализации: автор(ы) языка Nim забил(и) (в основном) на различия CamelCase snake_case nocase bAdCaSe в наименованиях. Хороший пример послать паразитную проблему подальше, не идеальный (в идеале в проекте есть спеллчекер в идентификаторах и правильная конверсия в CamelCase, snake_case, nocase по настройкам индивидуального разработчика — плюс строгое выпалывание bAdCaSe, конечно).

                                                                                                      Слабое место — поиск, но это технически решаемая проблема. Интересный комментарий выше — про полезность минимального Hungarian вроде "_" или «m_» префиксов, теперь уже из-за удобств IDE. С чего я начну новую строку моего идеального кода? Первый элемент намеренья обозначен довольно рудиментарным префиксом, а там уже — любимая мозгом рутинка, как в компьютерную игрушку играешь. Все довольны (главное, мозг :) ).
                                                                                                        0
                                                                                                        Я не пишу на .net, разрабатываю проекты на php, но каждый раз задумываюсь над этими и другими сотнями мелочей. При этом каждый новый проект ты начинаешь с идеей «Напишу проект лучше и круче по архитектуре, чем прошлый», а спустя 3 часа ловишь себя за копированием каких-то классов из старого проекта в новый. Да, ты закладываешь структуру нового проекта чуть лучше, но в ходе работы над проектом всплывают другие косяки, которые ты уже закрывал в старых проектах, но в этом не можешь, так как заложил все «круче» и «лучше». И вот ты снова пишешь неидеальный проект, круг замкнулся.
                                                                                                          0

                                                                                                          Любая система так работает. Её эффективность определяется самым слабым звеном. К системам, состоящим из людей, это тоже относится. Поэтому у того, кто владеет/управляет системой в целом, базовая задача состоит не в том, чтобы прокачать один из компонентов на максимум эффективности, а в том, чтобы все компоненты работали более-менее средне. С помощью формальных правил самых слабых можно подтянуть до среднего уровня, а самых эффективных, наоборот, опустить.


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

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

                                                                                                            Одна из причин, почему я не смог пользоваться райдером — это остутствие функции/экстеншена Shrink Empti Lines https://marketplace.visualstudio.com/items?itemName=VisualStudioPlatformTeam.SyntacticLineCompression
                                                                                                            Абсолютный мастхэв, без которого использование места на экране кажется варварством.

                                                                                                              0

                                                                                                              Со многим согласен, хотя пишу на PHP. Любимый стайлгайд — Doctrine. В нём, например запрещены суффиксы/префиксы interface/exception/abbstract. Почти не могу представить ситуацию, когда они важны, кроме интерфейса или абстрактного класса с единственной реализацией.

                                                                                                                +2
                                                                                                                но я за постфикс `Exception`

                                                                                                                Чуть менее чем все использования SomeThingException помещаются в два кейса:
                                                                                                                catch(SomeThingException ex)

                                                                                                                и
                                                                                                                throw new SomeThingException()


                                                                                                                То есть по окружению легко понять, что ты имеешь дело с эксепшенами, фтопку постфиксы ;-)
                                                                                                                  0

                                                                                                                  Кстати ведь да

                                                                                                                    0
                                                                                                                    outcome switch
                                                                                                                    {
                                                                                                                      { NotFound: { code : 404 } } => ...,
                                                                                                                      { NotAllowed: { role : "Admin" } => ...,
                                                                                                                    };

                                                                                                                    Чё-как, кто тут эксепшон?

                                                                                                                      +3

                                                                                                                      У вас тут Ok и Err/Right и Left не хватает

                                                                                                                        0
                                                                                                                        А есть разница, кто из них эксепшон?
                                                                                                                        Обычно это будет в контексте например catch блока или как то ещё. Очень редкий кейс, когда исключение ходит по коду не через throw\catch.
                                                                                                                          +1

                                                                                                                          logger.exception(E) или tracer.exception() — самые частые из этих редких :)

                                                                                                                      +2
                                                                                                                      Бойся желаний своих, иногда они сбываются. Настроение — оно как маховик работает. Так через N лет люди будут читать очередное нытьё уже бывшего программиста, ненужного индустрии. В целом же, подобный «мелочный» перфекционизм приводит к прокрастинации с забавными не очень последствиями, как неспособность писать код. Оно же не красивое, не идеальное, страшное. Отторгающе неприятное. И вот всё меньше полезных буковок.

                                                                                                                      Пха. «Король разработки». Неужели я таким же был, кхм… «Королём»…
                                                                                                                        0
                                                                                                                        А тут проблемы с платформой — в C# с моками всё очень плохо — если захочешь делать мок не на интерфейс, а на класс с не виртуальными свойствами и методами — пойдешь в жопу.

                                                                                                                        Это не проблемы, это хорошо. Я видел код на Python (в котором можно мокать почти всё), в котором для тест кейсов описывали штук пять-десять декораторов unittest.mock.patch (которые подменяют класс/метод на мок-объект), в том числе, мокающие некоторые приватные функции модуля при тестировнии его самого. Я вообще не уверен, что такой тест способен что-то отловить. Особенно, если учесть, что у питона динамическая типизация и даже изменение сигнатур зависимостей мы не заметим (они же замоканы!). mypy есть, но помогает не всегда.

                                                                                                                          0
                                                                                                                          Я видел код на Python (в котором можно мокать почти всё), в котором для тест кейсов описывали штук пять-десять декораторов unittest.mock.patch (которые подменяют класс/метод на мок-объект), в том числе, мокающие некоторые приватные функции модуля при тестировнии его самого. Я вообще не уверен, что такой тест способен что-то отловить.

                                                                                                                          Сам так делал. Это полезно когда есть легаси код с классами на пару тысяч линий, которые делают кучу всего. Например класс User у которого метод create, не только юзера создает, но еще и сообщение на почту отправляет, и аватарку генерит и т.д
                                                                                                                          И конечно же ни одного теста, и рефакторить страшно.
                                                                                                                          Вот и приходится извращаться поначалу.
                                                                                                                          0
                                                                                                                          если захочешь делать мок не на интерфейс, а на класс с не виртуальными свойствами и методами — пойдешь в жопу

                                                                                                                          Кстати, в C# были какие-то Shims и насколько я помню там можно переопределять методы любого класса, можно даже статические методы подменять.
                                                                                                                          Вот тут подменяют невиртуальный метод класса: docs.microsoft.com/en-us/visualstudio/test/using-shims-to-isolate-your-application-from-other-assemblies-for-unit-testing?view=vs-2019#instance-methods-for-one-runtime-instance

                                                                                                                          Или этим просто никто не пользуется?
                                                                                                                            0
                                                                                                                            Узбагойся и читай «Чистый код» перед сном.
                                                                                                                              +1
                                                                                                                              Моя война с большими файлами продолжается — я строго против xml-документации. Она всегда уродует код, иногда делает понятнее хреновый код, иногда дублирует информацию, представленную хорошим кодом. Братан, у тебя есть имя неймспейса, имя класа, имя метода. Имена параметров. Их типы, и имена этих типов. Если всех этих вещей тебе мало, что бы объяснить, как мне использовать твой код, документация тебе не поможет. Она просто замаскирует твою некомпетентность.

                                                                                                                              Желаю автору почитать MSDN без текстов, с одними лишь именами функций и параметров.

                                                                                                                              Only users with full accounts can post comments. Log in, please.