вторник, 15 февраля 2022 г.

[prog.c++.flame] Вторая часть моих пяти копеек про тестовое задание от Network Optix (по мотивам срачей на Хабре и RSDN)

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

Под катом, т.к. местами матерно.

А впечатление, прямо скажу, удручающее. От человека, который декларировал 15 лет опыта в C++, ждешь гораздо большего.

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

Ну вот вообще.

Простите мне мой французcкий, но блядь, как так можно?

Простое задание.

Простое условие.

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

Как? Как, мать вашу, так можно было?!!

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

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

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

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

Не зря же говорят "делай хорошо, плохо само получится".

Но нет, люди продолжают клепать код "на отъебись" и считают, что это нормально.


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

В первом решении с меня хватило двух фрагментов, связанных с подсчетом хэша в первом решении (фрагмент раз, фрагмент два). Там такое количество std::string на единицу площади, что о соблюдении каких-либо требований по эффективности и говорить не приходиться. Ну реально, человек в коде std::optional из C++17 использует, почему, етить-колотить, хотя бы std::string_view не применять?

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

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

Стремный момент номер раз: инициирование асинхронных операций чтения и записи без использования Asio-шного strand-а.

Почему этот момент стремный? Потому, что Asio запускается на пуле рабочих потоков. Что означает, что completion-handler-ы для асинхронных операций могут запускаться на любом из потоков из пула. И, потенциально, completion-handler для операции чтения может быть вызван параллельно completion-handler-у для операции записи. На разных рабочих нитях, естественно. Чтобы этого не было и предназначены strand-ы.

Может быть в Asio и есть встроенная защита от запуска двух completion-handler-ов, связанных с одним и тем же socket-ом (т.е. объект socket уже работает как strand). Но сомневаюсь. По крайней мере в RESTinio мы для многопоточного режима strand-ы использовали.

Если кто-то точно знает ответ на этот вопрос, то не сочтите за труд, отпишитесь в комментариях, плиз.

Стремный момент номер два: исходящие данные накапливаются в единственном объекте типа asio::streambuf. Но содержимое этого объекта может модифицироваться в то время, как этот самый экземпляр streambuf задействуется в асинхронной операции записи.

Подозреваю, что в силу особенностей асинхронного сетевого обмена в Linux-е, проблем именно под Linux-ом это может и не создать. Но сильно сомневаюсь, что такой фокус окажется работоспособным под Windows, где при использовании Overlapped I/O нельзя трогать буфер, из которого данные отправляются в сеть.

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

Опять же, если кто-то точно знает, как работает asio::streambuf, то отпишитесь в комментариях.

Ну и стремный момент номер три: точно ли автор решения понимает, как будет происходить завершение времени жизни его объекта Session в ситуации, когда клиент прислал 100500 мелких строк, а затем тупо закрыл сокет на запись на своей стороне (см. shutdown(s, SHUT_WR)) и перешел в режим чтения ответов сервера.

Насколько я понимаю, в такой ситуации все должно завершиться нормально. Нового асинхронного чтения не будет из-за вот этой проверки. А запись рано или поздно закончится вот здесь. Поэтому новых completion-handler-ов, в которых бы захватывался shared_ptr на Session больше не будет, старые completion-handler разрушатся и объект Session завершит свое существование.

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

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


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

Продолжение здесь.

Комментариев нет: