Pull to refresh
22
0
Алексей @pankraty

Разработчик

Send message

Я думаю, имеется в виду, что математически доказано, что лучший алгоритм имеет некую сложность, лучше, чем log(n) ^3 — например, log(n) ^2 (пишу наобум), но его пока не нашли. Из найденных у этого минимальная алгоритмическая сложность, проблема однако в том, что log(n)^3 становится хуже, чем sqrt(n) на довольно-таки больших n (миллионы или десятки миллионов), и с практической точки зрения это действительно делает алгоритм малоприменимым для реальных задач.
По крайней мере, я это понял так.

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

lock (_lock)
            {
                return _blob ?? (_blob = new Blob());
            }

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

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

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

В performance-critical коде первыми поставили бы наиболее быстро проверяемые условия (например, проверить булевские значения), а в конец — более медленные (например, сравнение строк), так что универсального подхода тут нет.

"Гитаре" читать как Гитлабе. Автокорректор не знает этого слова

Полностью поддерживаю! Тоже никогда не понимал этой страсти к физической доске со стикерами. Даже если предположить, что доска нужного размера всегда под рукой (а это не обязательно так), развернуть трекер в гитхабе, гитаре, трелло, да хоть в банальном Экселе ничуть не дольше. Зато возможности по редактированию, детализации, анализу, экспорту/импорту, дистанционной работе и т.д. и т.п. несопоставимо богаче.

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

Если вам это кажется нонсенсом

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


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

Что-то тут не так.


public static FileInfo SaveToFile(this IExportService self, Model model, string filePath)
    {
        using (var output = File.Create(filePath))
        {
            self.Save(model, output);
            return new FileInfo(filePath);
        }
    }

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

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

Вроде и грамотно пишете, но вот такой способ оценивания — путём сравнения с собой и своей зарплатой — обычно характерен для новичков. Скажем, я (бек-энд разработчик) задам кандидату какой-нибудь вопрос про ConcurrentDictionary, а он не ответит — и я такой "ммм, а я-то это знаю, значит, стою больше". А он может быть знает кучу других вещей, про которые я даже не подумал спросить, потому что сам с ними не работал в жизни. И может статься, что эти его знания позволят радикально улучшить проект?
В этом смысле грейды по принципу "джун должен знать X, мидл — X и Y, а сеньор — еще и Z впридачу" — действительно не имеют смысла. А если система грейдов отражает степень зрелости, готовности к самостоятельной работе и принятию решений — то она никому и не мешает и проблем особо не несет.

Ооо, я, кажется, вернулся в 2003-й — давненько мне не попадались холивары на тему Far vs WC/TC!


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

Выглядит, как переизобретенная концепция Middleware… Или я что-то упустил?

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


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

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

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


Из наиболее эпичных примеров, где плотное использование модульных тестов было настоящим life saver -ом — у меня был расчет перестраховочной премии. На входе всего три параметра — даты начала и окончания периода, да номер договора перестрахования, а на выходе — всего одно число. Зато неявных параметров — вагон и маленькая тележка. Поэтому отдельно проверяется логика (несамая тривиальная) отбора действующих договоров прямого страхования за период, отдельно — учет кумуляции в рамках застрахованного лица (там тоже кхм, интересные выверты в требованиях были), отдельно — логика преобразования валют (с учетом, что произвольный курс может быть зашит в договор перестрахования — а может не быть, и тогда надо брать курс ЦБ), отдельно — отнесение прямых договоров к тому или иному лееру договора перестрахования, отдельно — логика отбора бордеро предыдущих периодов, которые тоже участвуют в кумуляции и т.д.и т. п. E2e тесты просто непрерывно были бы красными, не давая никакой информации, на каком же этапе возникла ошибка. (Что не умаляет их нужности; они тоже нужны, но их одних тоже недостаточно).

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

При изменении бизнес-требований надо менять бизнес-логику И тесты. Ваш КО.


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

Это неверно, на множестве входных параметров есть классы эквивалентности, нужно их проверить.

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


А то, что вы проверили каждый метод в отдельности ничего не значит.

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

Юнит-тесты, конечно, не панацея, но они имеют два важных преимущества, которых не дают другие виды тестов:


  • простота тестирования граничных случаев. Иногда, чтобы протестировать поведение системы в каком-то очень специфичном случае может потребовать тонкая подгонка входных параметров — зачастую очень нетривиальная, требующая знания деталей реализации каждого из звеньев цепочки вызовов. В случае юнит-теста мы просто передаем граничные параметры, на которых хотим проверить работу определенного метода, в этот метод.
  • устранение комбинаторного взрыва. Представим модельный пример, в котором есть 4 метода, вызывающих друг друга, в каждом из которых выполнение может идти по 4 разным путям. В случае, если мы захотим качественно покрыть e2e тестами все пути выполнения, нам придется подобрать 44=256 различных сочетаний входных параметров, что может оказаться весьма непростой задачей, и очевидно, выполнено в полном объеме не будет — разработчик покроет тестами наиболее вероятные сценарии, понадеявшись, что и остальные работают корректно. В случае юнит тестов, нам может хватить 4*4=16 тестов, чтобы проверить все ветви исполнения кода — и это уже выполнимая задача. Это, конечно, все еще не гарантирует, что система работает корректно (одних юнит-тестов недостаточно), но сильно повышает нашу уверенность в ней.

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

Information

Rating
Does not participate
Location
Саратов, Саратовская обл., Россия
Date of birth
Registered
Activity