Pull to refresh

Comments 727

Полностью поддерживаю.

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

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

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

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

Я имею ввиду поддержку и расширение. С полиморфизмом можно посадить пять джунов делать новые классы паралельно. С ифами так не выйдет.

Как раз ифы проще для понимания и там сгодятся джуны.

Джуны уже понимающие ифы но еще не понимающие ООП, называются трейни.

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

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

Поэтому лучше писать их. Такой код лаконичней и понятней.

А потом иф вложенный в иф вложенный в иф... Достаешь бумажку, карандаш, и начинаешь разрисовывать все возможные состояния, и внезапно обнаруживаешь, что иф на 5-м уровне вложенности никогда не выполнится, потому что уже был перехвачен на третьем. Больше бумаги богу паутины!

Чем вы ифы замените в таком случае?

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

Золотая пуля - есть! Серебрянной - нет )))

P.S. Некстати говоря, я бы не сказал, что её совсем нет. Когда из Вашего кода уже просто нечего выкинуть, чтоб его улучшить - то вот оно. Лучшее решение.

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

Есть. Как нас учит ентот самый ТРИЗ, лучшее решение — это когда устройства кода нет, а его функция — выполняется!

вынесением каждого вложенного "ифа" в отдельный метод, функцию или целый класс, с именем обьясняющим что там этот "иф" делает?

на стейт машину

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

Все должно быть в рамках разумного, поэтому не согласен с тем как вы накорню отказываетесь от полиморфизма

Там можно методы бить на части..

.Чем вас спасёт если тот же метод метод voice распадается на десято abstractVoice, AnimalValidatirImpl, AbstractDogVoiceDefiner?

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


Там можно методы бить на части..

А можно и на классы с методами. ООП — это и есть метод организации кода.

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

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

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

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

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

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


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


interface IBinaryOperation<T> {
    T Op(T a, T b);
}

А вот такой интерфейс уже ничего не замедлит:


interface IBinaryOperation<T> {
    void Op(ReadonlySpan<T> a, ReadonlySpan<T> b, Span<T> result);
}
А вот такой интерфейс уже ничего не замедлит:

Ну, не так уж совсем ничего — вызов, да еще и косвенный (т.е. с выбором адреса вызова из памяти) — это небесплатно, особенно — с современной организацией оперативной памяти, кототорая в общем случае работает крайне медленно.
А потому я, к примеру, наблюдал, в коде ASP.NET как MS постаралась для наиболее массового случая — когда некий интерфейс (если кому интересны подробности — могу рассказать, какой именно) реализован стандартным классом ASP.NET — отдельно обработать вызовы методов именно этого класса как невиртуальные — а это ещё и может позволить встроить (inline) эти методы в код, чтобы вообще избавиться от вызовов.

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

Большими кусками не всегда получается.
Например, в том конкретном случае, про который я пишу, так была оптимизирована операция создания контекста обработки запроса HTTP. А запросы HTTP — они по жизни, чаще всего, короткие (особенно если это — CRUD, столь желанный автору статьи ;-) ). Но зато этих запросов может быть много.

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

Так а с чего вы взяли что полиморфизма там нет? Не забывайте что у полиморфизма есть разные измерения — set of objects vs set of features. Поэтому например существует паттерн "визитор", который часто используется в местах иерархией которая редко меняется, но зато функционал часто разный. А визитор это просто switch in disguise по большому счету.


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


Поэтому тут нет никакого противопоставления.

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

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

В номральных языках есть pattern matching с completeness и redundancy check. Сложно становится накосячить.

С#, Kotlin и даже, внезапно, Python. Но у последнего сложнее с completeness и redundancy check.

Увы, нормального completeness check в C# не завезли:


enum Foo
{
    Bar,
    Baz,
}

var x = foo switch
{
    Foo.Bar => 1,
    Foo.Baz => 2,
};
// warning CS8524: Выражение switch не обрабатывает некоторые типы входных значений, в том числе неименованное значение перечисления (не является исчерпывающим). Например, не охвачен шаблон "(Foo)2".

Ну это если не читать спецификацию языка

А что я там должен прочитать?

Задача-то проверить на этапе компиляции, что все случаи обрабатываются. Чтобы при появлении новых случаев компилятор предупредил если их забыли добавить в switch.


Так вот, эту задачу связка enum+switch в C# не решает. Можно сколько угодно говорить что программист дурак и не понимает как работают enum, а компилятор делает всё правильно, но задача от этого не решится.

Задача-то проверить на этапе компиляции, что все случаи обрабатываются.

Как всегда есть нюанс. В C# используются C_style enums. То есть (Foo)25 это тоже корректное значение и компилятор проверяет что и его тоже покрывает switch-expression.

Чтобы при появлении новых случаев компилятор предупредил если их забыли добавить в switch.

Ну конечно нет.

Так вот, эту задачу связка enum+switch в C# не решает. Можно сколько угодно говорить что программист дурак и не понимает как работают enum, а компилятор делает всё правильно, но задача от этого не решится.

Программист-дурак это тот, кто думает что знает как должно работать, но не читал спецификацию.

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

UFO just landed and posted this here

В Dart 3.0 добавили patterns, вместе с switch expression, который как и switch statement проверяет exhaustiveness.

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


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

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


Например?

fn foo(x: Option<i32>) -> i32 {
    match x {
        Some(val) => val   
    }
}

error[E0004]: non-exhaustive patterns: `None` not covered
 --> src/lib.rs:2:11
  |
2 |     match x {
  |           ^ pattern `None` not covered
  |
UFO just landed and posted this here

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

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

Согласен, сначала делать просто, потом рефакторить. Главное не пропустить момент.

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

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

Был у меня случай, на с++ для UDK делал физическую модель машины(Камаза) на 4 колеса. Пришел техлид и переделал все под шаблоны и 4 колеса превратились в 3(ТРИ!!!). Снес его все правки и вернул как было.

Если смотреть с определённого ракурса, у Камаза будет видно только 3 колеса :)

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

Интересный у вас техлид, однако...

Это база.

Есть такие магические буквы S O L I D. И почему то все забывают что это не ПРАВИЛА. Это вообще-то РЕКОМЕНДАЦИИ. И им необязательно следовать.

На самом деле, мне кажется, ценность синьёра именно в умении БЫСТРО решать задачи. Если он ебать прошаренный, то он точно будет знать когда эти ваши полиморфизмы нужно использовать, а когда, как в примере выше.

У меня на работе была задача сделать миграцию из оракла в постгрес. И значит 2 команды параллельные. В одной я, в другой другие ребята.

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

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

Что сделал я: скачал ora2pg и сутки разбирался почему руби не работает на моём компе. А после запустил и всё за пару часов отработало...

Пришел тимлид в мою команду и тоже начал разрабатывать свой инструмент.... За две недели до нг мы всё равно мигрировали через ora2pg.

А можете всеже пояснить, что не так с OneToOne (если я правильно понял о чем речь)?

Если у тебя две таблицы, связанные один к одному - так зачем тебе две таблицы? Сложи все данные в одну.

В основном Иногда такое встречается в легаси. Когда у тебя просто нет возможности добавить новое поле ничего не сломав в системе, которая писалась 20+ лет многими поколениями программистов (да. Тестов нет. От слова - "совсем"). Тем более если ты понимаешь, что это поле востребовано будет раз в год по понедельникам (либо вообще не приживётся и через полгода "бизнес" про него забудет, а тебе с ним жить)

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

ну и другие моменты например User - Profile. первое нужно всегда, второе почти никогда. удобнее делать SELECT *, не указывая конкретный список полей.

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

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

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

Поправьте пожалуйста, если я в чем-то не прав

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

Да, TOAST это исключение скорее :)

Ночь. Не заметил, что о картинках речь в вашем комментарии была

Но коллеги могли и не ставить минуса, а пару слов написать хоть :) помочь так сказать

Да не, нормально всё. Т.к. Фактически там "ересь" написана. Так что пусть остаётся заминусованым, раз уж я его исправить не могу. Вдруг кто-нить из поисковика попадёт на тот коммент и решит, что это правда, т.к. Сообщество на это никак не отреагировало.

В смысле "любит"? Он по определению делает новую запись при каждой записи каждой строки. Это не потому, что он любит/не-любит, и не потому, что разработчикам было лень заморачиваться. Так реализована "версионность" для обеспечения всяких разных уровней изоляции транзакций. By design, как говорится.
Вот из "первых рук" https://www.youtube.com/watch?v=SNrv8ZPpPfg&list=PLaFqU3KCWw6LPcuYVymLcXl3muC45mu3e&index=6

UFO just landed and posted this here

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

Контрпример :

Была база с картинками. Служебной инфы минимум, а картинки большие. Вся таблица в память не влезает, поэтому каждый запрос тормозит обращение к диску.
Переделали на 2 таблицы: в одной инфа о картинке (с маленькой превьюхой), в другой сами картинки. Скорость выросла в несколько раз.

Да, хороший пример. Некоторые типы данных плохо уживаются в одной таблице.

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

А вы сами хороший разработчик, верно? Можете спроектировать схему как хранить данные для товарища выше: пяток служебных полей по 32 байта каждая и картинка на 10мб. Как будут хранить это добро в одной таблице разработчики, которые умеют проектировать схему?

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

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

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

Мне иногда кажется, что Амазон лучше видеть на картинке.

Я вот потрогал, -NN тысяч нерублей.

Радует, что я стал опытнее не за свой счёт, а за счёт компании, но мне кажется, что некоторые сервисы в интернете прямо-таки сделаны из "темных" паттернов.

Тот API, который задал S3, переусложнён и является первый кандидатом на применение принципа YAGNI. А известная мне клиентская библиотеки для этого API усложняет всё ещё больше...


Главное достоинство S3 — это возможность ограниченно делегировать мониторинг свободного места на диске другой команде. Если вы не в облаке и у вас нет второй команды сисадминов — S3 вам не нужен.

Легко: Укладываем все в одну таблицу.

Я не скажу за все БД, но в MSSQL, MySQL и PostgreSQL блобы размером 10мб будет храниться в отдельных страницах. А если правильные типы подобрать, то оверхед от блобов будет не больше одного указателя в основных страницах таблицы. И почему-то я уверен что в Oracle будет также.

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

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

Конечно они её ускорят, но совершенно не по тем причинам, по которым они думают что ускорят.

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

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


Думаю люди среагировали бы куда мене бурно, если бы эта инфорация была в первом комменте вида "а вы знаели что бд X,Y,Z автоматически это оптимизируют и вам не нужно ничего делать? Вот ссылка на документацию (линк)". Для меня например это была новая информация, спасибо за нее. Впрочем, как я ниже говорил могут быть и другие причины для подобного разделения. В свою очередь считаю что одно из качетв хорошего разработчика — отсутствие категоричности в высказываниях/суждениях.

Очевидно это зависит от движка базы. 

Конечно зависит. Учитывая что движок реляционной БД пилят люди разбирающиеся в теме гораздо лучше меня, то я буду предполагать что там эта проблема решена. А если что-то не получается, то это я не делаю что-то не так.

 какой-нибудь SQlite

Вы удивитесь https://www.sqlite.org/fasterthanfs.html

Но вообще для встраиваемой базы хранить картинки - непонятная затея. У нее варианты использования сильно отличаются от серверов баз данных.

очередная революционная рейвендиби может не иметь

Это вы уже пытаетесь юлить, чтобы ваши утруждения не выглядели столь неубедительно. Но очевидно речь идет о взрослых СУБД, которых в нашей реальности существует 4-5 штук.

Думаю люди среагировали бы куда мене бурно, если бы эта инфорация была в первом комменте вида "а вы знаели что бд X,Y,Z автоматически это оптимизируют и вам не нужно ничего делать?

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

В какой-то момент программист, увидев что программа работает медленно, посчитал что это не его вина, а вина БД, а он такой умный сможет решить. И, к сожалению, решил, что увеличило уверенность в себе.

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

Если вдруг мой код, который работает с БД оказывается медленным, то я думаю что это я что-то не знаю или не так сделал, а не база плохо работает

в основном да. Но иногда бывают нюансы. у меня был случай, когда я в оракле (по-моему 11g) в существующую таблицу решил добавить поле xmltype. Что могло пойти не так? На первый взгляд ничего, тем более что я даже не собирался по этому полю ничего искать. Мне нужно было просто хранить xml. Но внезапно у меня стали тормозить простые запросы вида "select * from my_table". Т.е. Даже без условий (и по-моему, даже с незаполненным этим полем, но это не точно).

Я полдня убил, пытаясь понять кто дурак. В конце концов плюнул и сделал поле обычным "clob".

select *

Это вы уже сделали неправильно.

Это просто пример. За давностью лет подробностей не помню, но по-моему тормозить начинало просто на ровном месте, когда поле xmltype попадало в выборку.

p.s. По поводу неправильности select * я бы не был столь категоричен. Иногда это бывает необходимо.

Картинки в БД - это уже антипаттерн.

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

Вроде текстовые поля переменного размера РСУБД и так отдельно хранят. Хотя может от реализации зависит.

В разных базах разные ограничения на типы и размеры, но в целом да

Текстовые поля можно хранить в отдельных страницах и не поднимать при каждом запросе.

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

Вы о какой СУБД? В mssql типы text и image хранятся в отдельных страницах всегда. Не поднимаются если не указаны в select. типы varchar и varbinary сохраняются в отдельных страницах при размере более 2000 байт.

В postgres то же самое происходит типами text и bytea по умолчанию, можно настроить чтобы они всегда сохранялись в TOAST (отдельных страницах)

Я уверен что и в других БД есть подобные механизмы.

С точки зрения клиента БД надо только в select не указывать поле, чтобы картинки даже с диска не считывались.

Речь же выше о тейблспейсе. Вы никак одну таблицу не разложите на разные тейблспейсы. А две в 1-к-1 легко.

Что такое тейблспейс? Зачем на них раскладывать таблицы?

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

Вполне нормальные случаи, когда у нас нижележащий уровень отдает диски по 2 (5, 10, ваш вариант подчеркнуть) ТБ максимум (в силу их физических ограничений). И тут уже начинаются либо всяческие ужимки, либо объединение дисков на уровне ОС (с непредсказуемыми, иногда, последствиями).

ну да, такое вполне может быть и там проще сразу настроить тэйблспейсы на разные диски и партиционирование для самых крупных таблиц, но такой кейс на несколько хранилищ на сервере с базой я не встречал ни разу — обычно были VPS с одим диском, либо сервера с небольшим системным диском и хранилищем большой емкости, на которое основной тэйблспейс и мапится, понятно что если дисковое пространство забьется придется что-то с этим делать — либо масштабировать (но в случае с облачными серверами это в принципе делается просто средствами ОС и при помощи одной крутилки и 3,5 команд) либо добавлять хранилище и мапить на него новый тэйблспейс с настройкой партиционирования

обычно были VPS с одим диском

я однажды упёрся в лимит дисков на VMWare vCenter. Сказали, увы, столько дисков подключить нельзя (к одной виртуалке)

Каждый из них был, ЕМНИП, по 2 ТБ.

ОПСОС, биллинг.

Одну можно партицировать

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

Бывают разные картинки, разные БД, разные требования.. ИМХО что-нибудь типа скана паспортов весьма разумно сделать в табличке в БД, да еще и зашифровать, а не выкладывать на какую-то файллопомойку, и связь там скорее всего сделают сначала 1к1 к табличке Users (но потом вдруг внезапно окажется что человек может менять паспорта :) ). А вот котиков в телеграмме в БД наверно класть не очень...

Подскажите, пожалуйста, а какие в данном случае паттерны существуют? (вопрос без подтекста, если что, искренне интересно узнать новое)

  1. файлы хранить в даталейке типа S3, в бд можно ссылки на них

  2. таки хранить файлы в базе, часто это ок, как в коменте выше

UFO just landed and posted this here

S3 по мне тоже в топку - это зависит от использования, но деньги тратить за каждый запрос??

S3 давно уже существует в отрыве от AWS, в том числе on-premise. Куча систем хранения умеет его предоставлять.

Только там есть ещё одна проблема. Orphaned objects ... Когда вумники накидали туда всякого, а куда и что забыли. А так, да on-premises просто как грязи.

Это проблема любого объектного хранилища -- хоть ФС, хоть S3.

Файл забыть удалить в ФС ничуть не сложнее, чем объект в S3.

Только в БД хранить файлы может от этого спасти, но, как верно отметили выше, это может создать проблемы производительности и удобства.

S3 это уже много лет не только Amazon.

Во-первых, есть провайдеры, где внутренние запросы к S3 бесплатны внутри локалки (соответственно, простейший nginx проксирующий, либо прокси на уровне приложения делают весь трафик бесплатным) и платишь только за объем.

Во-вторых, есть self-hosted OpenSource решения. Например, minio.

Но при этом S3 масштабируется удобнее, чем простые файлы.

У minio достаточно давно GNU AGPL v3, что существенно ограничивает self-hosted применение.

Во-вторых, есть self-hosted OpenSource решения. Например, minio

Зачем это надо, если развитые способы работы с файлами есть ещё со времён бейсика.

Но при этом S3 масштабируется удобнее, чем простые файлы

С чего это вдруг? Простые файлы прекрасно масштабируются. GlasterFS и Ceph не вчера придумали.

При этом они ещё стандарты POSIX поддерживают. А поддерживает ли S3 стандарты POSIX? Можно ли ,например, залочить файл на S3? Можно ли сделать атомное переименование?

S3 != AWS S3. Есть куча совместимых по API решений. По факту это как раз готовый компонент который управляет файлами как нужно. При этом дает тебе готовый REST API для работы с файлами

С чего это тупость? Опять религиозные рассуждения?

Давайте уж по-инженерному. Мы же технические специалисты, не так ли?

Чем, собственно, БД хуже чем файловая система? Вы прям действительно считаете, что инженеры PostgresSQL не предусмотрели эффективного доступа и хранения Блобов?

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

Суммарный размер бэкапа БД и картинок не должен сильно различаться в обоих случаях (в теории).

ценность картинок как правило меньше чем ценность данных

С чего это вдруг??

файлы хранить в даталейке типа S3

S3 - чрезмерное и неоправданное усложнение (в большинстве случаев).

Я, честно говоря, не понимаю в чём преимущество S3 перед обычными файлами, а проблем - выше крыши.

Конечно можно попробовать подмонтировать S3 на файловую систему для упрощения доступа. Но непонятно как хорошо будет работать. Скорее всего и медленнее и менее надёжно.

Суммарный размер бэкапа БД и картинок не должен сильно различаться в обоих случаях

У PG два вида блобов. По умолчанию унитри PG хранит блоб в текстовых полях и делает глобальную экранизацию символов. Поэтому размер там растет. И ещё минус - эти данные не стремятся. Стриминг эмулируется (выкачивается весь блок в память и после стримится).

Зато просто обеспечивается backup/restore через те же текстовые форматы, это да.

s3 я привел как пример потому что у меня и база в облаке. А если база в облаке, то файлы с картинками 100% будут там же.

Но хранить в базе тоже можно, главное просто знать что и зачем мы делаем.

А хранить картинки на диске не позволяет религия?

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

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

Т.е. у вас БД - это что-то такое эдакое, витающее в воздухе, а не хранящееся на том же диске? Какая интересная у вас БД

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

Если подобных данных (назовём их "картинки") немного и для работы с ними требуются вышеперечисленные фишки, которые уже есть в бд, то - почему нет?

Вы всерьез не видите разницу между кластером из БД, которые пользуются своими своими жесткими дисками в экслюзивном режиме и NFS, которую примонтируют к себе пара десятков микровервисов?

Впрочем, если у вас монолит, который запускается на голой виртуалке, то да, проще прямо в /opt/kartinki сохранять, конечно

Жила была одна база, с 2 организациями и приложенными файлами к документам внутри (точнее файлы таки были на диске). Решили ее разделить по организациям. Сделали копию и получили 2 одинаковых базы. Потом в одной копии удалили документы по одной организации, в другой — по другой. Угадайте, что осталось на диске с приложенными файлами.

Тут возможны варианты от "ничего" до "всё", в зависимости от радиуса кривизны рук))

В данном случае оказалось ничего. Так как копия БД это же копия БД, а не копия еще и связанных файлов с пересчетом ссылок на них. В общем как правильно ниже пишут, копирование превращается в пляски с тем, что движок БД умеет отродясь (транзакционность, целостность, бэкап (в том числе инкрементальный) и тд )+ холивар про 1-1, так как хранение объектов вне БД это именно оно и есть. Одной ссылке в БД соответствует одна картинка на внешнем носителе. А если можно сослаться на одну картинку можно из разных мест без промежуточной таблицы в БД — то привет счетчик ссылок для определения, когда же ее с диска удалять.

UFO just landed and posted this here

Эм, база данных это масштабируемое решение, которое легко кластеризируется, позволяет сделать доступ к одному "полю" от имени множества клинетов одновременно, делать множественные выборки и быстро искать. А файлики, если их пару миллионов - уже нужно писать велосипед для раскладывания по папкам и париться про Name convention

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

UFO just landed and posted this here

Ну, не так уж БД и легко кластеризуется, если от кластера требовать суммирование размера хранилища, а не быстродействия.


Но соглашусь что файлы это делают ещё хуже.

Это у вас ФС – это что-то витающее в воздухе, а не БД.

ФС вообще то тоже БД, и функциональность/гарантии этой БД далеко не всегда подходят.

не создает проблем с репликацией и резервированием БД из-за большого объема данных

Тут или картинки нам жизненно важны для бизнеса (и тогда пофиг на сложности с репликацией, она и так, и так будет), или их можно достаточно безболезненно потерять - и тогда пожалуйста храни на shared storage. Хоть файлами, главное чтобы inode не кончились. Потому что добавление +1 решения для хранения картинок потребует железа для этого решения, репликации для этого решения, поддержки этого решения, мониторингу этого решения, и ещё вероятно распределённых транзакций. А, ну ещё чтобы бэкапы не разъехались, когда база у нас в статусе на пять часов утра вчера, а картинки в статусе на cемь тоже вчера, т.е. там есть то, о чем БД не в курсе. Наоборот - веселее...

пофиг на сложности с репликацией, она и так, и так будет

Используйте например CouchDB, и никаких сложностей с репликацией картинок не будет.

бэкапить, переносить, поддерживать целостность вручную?

У меня такой же вопрос возник...

UFO just landed and posted this here

переделать 1-to-1 на 1-to-N вообще нету никаких проблем. Это когда наоборот - проблемы

Хм. По-моему 1к1 это частный случай 1кN. Т.е. В базе с отношениями 1кN запросто могут встречаться случаи 1к1 и это нормально и ничего не ломает. А вот если было 1к1, а стало 1кN, то внезапно многие запросы начнут возвращать больше строк чем ожидалось, что может стать неожиданностью.

Если таблица с реальными картинками ещё и реализация on-disk стораджа (1 запись = 1 файл, без кэширования в памяти, кроме того, что сам файловый массив даёт - FileTables в MSSQL, в других субд вроде как тоже были аналоги, либо бэкэндом реализуемо), я-бы даже похлопал. А то чудесатые многотеррабайтные гидры вроде Sharepoint и прочих традиционных usecase для MongoDB не перестают удивлять, каждый раз когда на них натыкаешься.

Вы в курсе что это все база данных умеет делать под капотом? Вам достаточно правильную проекцию написать.

Написание проекции нарушит правило про круды

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


Это это нарушает правило "всё делать через CRUD ничего не вдумывая", ведь в CRUD только одна операция Read.

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

Может, и не вы придумали. Но в те древние времена, когда БД были в новинку, никакого такого правила не было — по крайней мере, такого правила не было в той старой книге Мартин Дж. "Организация баз данных в вычислительных системах"(1980 г. издания перевода), по которой я БД изучал. Наоборот, преимуществом БД над плоским файлом как раз считалось то, что приложения могут без проблем работать только с тем набором полей БД(сущностью), который им интересен, полностью игнорируя наличие других полей — которые для других приложений (да-да, в те времена с одной БД могли работать несколько приложений).
Но любители все упростить, чтобы лишний раз не думать (а среди веб-программистов я таких видел много, по крайней мере, лет 20 назад) конечно, могли придумать себе такое правило: "одна сущность = одна таблица" и наоборот. Только не думаю, что это — хорошее правило для любых случаев.

Я тоже так не думаю, а вот автор об этом в разделе "Системность" так и пишет:


Любой процесс вы должны привести к КРУД и запихнуть его в стандартную цепочку классов. РЕАД, АПДЕЙТ, ДЕЛЕТЕ. Любой бизнес-процесс вы обязаны декомпозировать на серию логичных КРУД-операций, будь-то заказ пиццы или перечисление денег.

То есть должен быть строго 1 Read, а два Read это уже антикруд?

Это уже даблкруд. Если Read три, то соответственно триплкруд

В контексте обсуждаемой статьи — да. Иначе вы сеньором станете!

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

Это как, 'SELECT * ' на таблице с картинками?
Я к тому, что все данные по картинке могут находиться в одной таблице, просто SELECT делайте для тех колонок, которые нужны в контексте задачи.

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

Мы же про 1:1 relation?
Про какую "колумпнсторную" базу речь?

А почему картинки в базе? Ещё скажите что фронту из в base64 отдаете, как и храните.

Таблицы на 30 + полей не так то удобно таскать как кажется.

Процессинговая транзакция на 300+ полей застенчиво улыбается

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

У меня таблица пользователей и вся инфа в них ФИО , активный пользователь и системные поля дата создания итд

И меня бесит когда добавят туда ещё 100500 полей

Почему не создать отдельные таблицы адрес и телефон
Как пример естественно

И создать связь один к одному?

Зачем делать и таблицы склад в 100500 полей, и потом думай,что за что отвечает

И далее такие связи можно легко перевести на один ко многим,в отличии когдв все в одной таблице

Как минимум в два раза больше строк кода? Еще Повышенная сложность.

Для вас связка из двух таблиц это повышенная сложность?

По-моему вопрос на собеседование про join является базовым

Про строк больше в два раза,не понял ничего ((

А если машину поцарапать маленьким гвоздиком, то можно? Только большим нельзя? И ксати да. Там ленивая инициализация или нет, связывается одинаковыми ID, или вторичными ключами, как настроить ленивую, там по умолчанию жадная, НУ и просто я минут 5 матерюсь, когда oneToOne вижу. А потом запросики писать, каждый раз джойны дописывать. Inheritance гуглить-писать. Все что усложняет, пусть хоть на миллисекунду понимания - все плохо.

То есть я правильно понимаю ваш подход
Пихать все в одну таблицу и без разницы, что она будет разрастаться, со скоростью появления у дурака фантиков и пусть весь мир подождёт?

Если у юзера есть 150 уникальных полей, рост, вес, диаметр ноги и так далее, то да в таблице будет 150 полей. Может после 200 полей я подумаю про 1к1.

Это вопрос про дизайн и про размер базы (число юзеров). Если юзеров 100 и будет 1000 ближе к тепловой смерти вселенной, то нормализация не нужна. Хотя я не понимаю такой аллергии по отношению к нормальным таблицам. Пример кстати говорящий - если у юзера 150 полей, то, по вашей же логике, это проектировал синьор.

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

Повторюсь, если у вас в базе 10 связанных таблиц, и как программисту вам приходится делать селект с 33 иннер-аутер-лефт-райт джойнами, обмазываясь констрейнтами и развивая себе абстрактное мышление до визуального представления 11-мерного пространства, синьор в данном случае - это вы. Миддл бы посоветовался с DBA и сделал встроенную функцию. Причем запрос для нее и профайлинг вполне сделает DBA на радостях, что хоть кто-то решил сделать решение не через зад. И да, под каждый запрос - функция. Нужно новое представление в программе - новая функция в базе. Это просто.

p.s. я не DBA и никогда им не был.

Это если вы настолько крупный (в смысле организация), что у вас есть отдельный DBA.

А если его нету, то мидл вместо запроса с лефт-райт-джойнами накодит find()->joinWith()->all() через ORM и будет в коде приложения разгребать то, что получилось.

Нужно новое представление в программе - новая функция в базе. Это просто.

Эх, да. А потом Oracle меняет политику лицензирования, вы очень хотите вот прям завтра съехать на PostgreSQL, но что-то крепко держит под водой за яй... ногу.

Переводил несколько проектов с Оракл на PG. И да, там кол-во функций было от 300 до 5к. Успешно перевели. По времени от 1 месяца до года занимали трансформации (но там нюансы были с тем, что не должно было быть простоя при переключении).

сотнями миллионов

подозреваю в таком случае врядли там 1-к-1

Может после 200 полей я подумаю про 1к1.

Непорядок, что это еще за magic number 200? Надо идти до конца

Чтобы таблица не разрасталась, надо отобрать у дурака фантики.

class User < ApplicationRecord
  ...
  belongs_to :address, optional: true # указали связь, что у пользователя может быть адрес
  delegate :address1, :address2, :city, :state, :zip, to: :address # делегировали поля
end

# пример использования

user = User.last
user.address.city == user.city # это одно и тоже значение из одного и того же поля в БД

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

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

Ну и в запросах к базе у вас добавится строчка (а ещё интересней будет с выборкой нескольких пользователей с адресами) ;)

Хотя, это не так сложно, и я мнение автора не разделяю. В некоторых случаях связи 1:1 нужны.

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

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

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

Почему не создать отдельные таблицы адрес и телефон

Потому-что потом начнется: "А чего это мы адрес храним одной строкой? Нужна гранулярность! Разбиваем адрес на: Индекс, Регион, Район, Город, населенный пункт + Улица, дом+корпус+квартира. Ещё геокоординаты прикрутим. Зачем? А вот затем! Вдруг нам нужно будет быстренько всех сотрудников объехать, или понять кто с кем близко живёт! Вы мне потом спасибо скажете!"

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

и номеров телефона у человека может быть сильно больше одного

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

Как минимум полезно, если вы что-то клиентам доставляете, и у вас не флетрейт по всему миру.

Может CREATE VIEW решит вашу проблему — и вы эти стопитсот полей никогда и не увидите (ну, разве что когда специально посмотреть)?
А если нужно в таблице ещё и править/добавлять, то и для этого есть средства: например, в MS SQL — Updatable Views, а лично я в стародавние времена в Interbase, в который всяких вкусностей мало завезли, триггеры для этого использовал

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

UFO just landed and posted this here

Еще не встречал систем, где адрес бы определсялся независимо. Даже несмотря на КЛАДР и подобные системы ключем адреса обычно бывает "чей адрес", "какой тип адреса", а не собственно адрес. Таким образом для двоих в одной квартире будет 2 адреса в таблице адресов.

UFO just landed and posted this here

Ну, для целей переписи, наверное, нужно :)

по твоей логике и manyToOne не нужен, ну пиши тоже все в 1 таблицу. Понапридумывали каких-то там нормальзаций бд, да еще и по несколько видов. Бред какой-то

Если у тебя две таблицы, связанные один к одному - так зачем тебе две таблицы? Сложи все данные в одну.

Простая задача пользователи и роли. У пользователя может быть только 1 роль, роли можно добавлять динамически.

Это не oneToOne, у ролей может быть много пользователей в общем случае

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

А один тариф может быть у многих пользователей

Ай-яй-яй, и правда, мой косяк.

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

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

Многие БД умеют разделять права ролей не только "построчно", но и "поколоночно". Стучишься в номер паспорта и получаешь NULL. Или что-то по маске.

многие, но не все.

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

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

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

Это не oneToOne, это словарь/справочник/реестр. Отдельный паттерн проектирования бд, когда перечислимые общие данные выносятся в отдельную таблицу. По факту это многие-к-одному.

С технической точки зрения документ-родитель связан со своим наследником отношением 1 к 1

Справочник ролей и справочник пользователей - отдельные таблицы. У каждой из этих сущностей свои атрибуты.

А если у меня три и более таблиц? Есть центральная таблица объектов, но в зависимости от типа объекта ему сопоставляется дополнительный набор параметров в одной из таблиц.

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

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

Да, в вашем случае получается плюсов у варианта с центральной таблицей больше.

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

Так это тоже не 1-к-1.

Подскажите, а почему решили так разить, а не таких три таблицы: Таблица объектов. Таблица параметров. Таблица с соответствиями ИД объекта и ИД параметра?
и уже на уровне бизнес-логики определять какому типу объекта могут быть сопоставлены какие параметры, модель данных готова ко всем вариантам, в том числе появляется гибкость для кейсов "в некоторых случаях работают со списками объектов без учёта типа".

это называется EAV, может быть очеень серьезным антипаттерном при проектировании БД.

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

О нет. Это как раз строгий антипаттерн. Если вы задумали такое, то надо подумать еще раз. Возможно в сторону не sql БД.

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

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

Но свою задачу решает. Сотни миллионов атрибутов MySQL ворочал достаточно шустро.

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

Это не звучит как задача от бизнеса.

Тут надо откатываться еще дальше и идти говорить с бизнесом о том что же он на самом деле хочет. Как правило оказывается что их неограниченное количество атрибутов оказывается равно или меньше 42.

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

Вы серьезно? Это без проблем типизируемая информация которая хорошо ложится в примерно десяток (из них 5-6 это адрес) разных колонок. И рядом текстовая колонка Другое. Экзотики на самом деле не так много.

Не надо оверинжинирить на пустом месте. Хочет клиент указать дорогу на оленях? Да не проблема, вот текстовое поле. Если у нас не система учета оленеводов, то ради него одного делать ничего особо не стоит.

Да в три поля это решается — контрагент, тип контактной информации, собстственно контактная информация. То есть EAV в чистом виде

И даже в одно решается. Если туда джейсон положить. Вопрос что с этим дальше делать. И зачем вообще все это.

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

У вас альтеры в БД платные? Я не понимаю этого страха изменения схемы БД. Это абсолютно нормальная операция. Она делается на проде без даунтаймов.

Я правильно понял, что значения параметров в предложенном вами подходе хранятся в одной колонке, схоже с Entity-Attribute-Value?

Я стремился упростить применение средств базы для обеспечения целостности данных, их согласованности в помощь тем проверкам, которые есть на уровне приложения (всякие not null, unique(param1, param3, param17), триггеры) для каких-то наиболее критичных штук + ускорение поиска и тех же проверок уникальности за счёт использования индексов. Приятный бонус - читабельность таблиц.

Поскольку добавление нового типа объекта происходит редко, может раз в год или два (и предполагает новую функциональность), всё это не превращается в огромные switch-case по типам.

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

так тут тем более не вариант оставлять их в основной табалице

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

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

после select вместо * можно перечень только нужных полей сделать

Читать Select 30_названий_полей From table where условие_по_полям_1,2,5,9,17,28 тоже такое себе развлечение.

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

База все равно грузит записи в память целиком

если таблица не columnstore, с диска всё равно поднимутся все поля

окей, есть ещё два частных случая (хотя на счёт row overflow у меня нет твёрдой уверенности, что это справедливо для всех случаев), когда при использовании списка столбцов в память поднимутся не все поля

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

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

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

прикол в том, что вы сами выдумали тейк про каждый столбец-таблица и сами его осудили

индексы на каждый чих тоже не бесплатное удовольствие

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

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

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

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

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

ну и я видел красивую таблицу со 170 столбцами и 90 индексами (не в хранилище) - это относительно того, что я увижу, или не увижу

А ещё есть пример, когда мы делаем модульное приложение и модули к нему пишутся отдельно (может быть даже другой командой).
И вот тогда сущность-из-модуля может иметь 1-to-1 связь с сущностью из основного приложения.

Так делают в Django, когда надо добавить к пользователю, который идёт из коробки, новые поля.

А если у меня есть таблица User где есть вся информация о пользователе и таблица UserAuthenticationData с данными для авторизации - их тоже мешать?

А если у меня есть разные по природе сущности - например машина и двигатель, мне их тоже в одну таблицу пихать?

А если я не люблю таблицы по 50 полей?

Возможно есть разница между 1-to-1 и 1-to-0..1

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

И когда мы будем работать с одной сущностью предметной области, мы будем блокировать другую, потому что на уровне БД храним это в одной таблице?

Звучит как ерунда какая то.

А с чего вы взяли что это разные сущности? Разделив данные пользователя и логин вы что выиграете?

Что значит "блокировать"? Вы обновляете поля a,b,c в одном методе, и поля x,y,z в другом.

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

Блокировать - rowlock хотя бы.

А если они ошиблись? Массовые заблуждения не такая уж редкость как кажется.

rowlock хотя бы

И в чем проблема?

А зачем лочиться, если можно не лочиться?

И как вы собрались не лочиться? Запрос у вас полюбому повесит rowlock как минимум на одну строку.

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

Я за первый вариант.

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

У вас любой запрос лочит строки. Лок двух срок в двух таблицах съест чуть больше ресурсов, чем лок одной строки в одной таблице.

А лочить первую (мета) таблицу не обязательно, например, когда у нас много задач UPDATE второй таблицы.

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

id | balance

А основную таблицу

id | passport | reg_address | real_address | contact_phone

вы даже не трогаете.

проблема в том, что это искусственный пример. А реалистичных примеров где уместно 1-к-1 хрен найдешь.

Безусловно, искусственный.

А в реальной жизни у меня табличка "пользователи" содержит 70 полей, одно из которых - json :) еще на сотню+ полей. Весит несколько терабайт.

И да, там есть (точнее, с недавних пор - было) поле balance.

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

Их полно.

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

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

Какой-нибудь download manager может держать основную сущность скачивания в одной табличке, а метаинформацию вида скачано\размер - в другой. Добавление ссылки в систему создает запись только в первой таблице, начало скачивания - во второй.

Вопрос же бизнес сценариев работы с этой информацией.

Имхо, есть случаи, когда one-to-one вполне себе уместно использовать. Условно, есть список файлов. Каждый файл можно опубликовать отдельным постом. У поста могут быть теги, превью, комментарии и т. д. Если держать всё в одной таблице, то получим странного виду таблицу для файлов со множеством nullable полей, которые нужно было бы каждый раз исключать при работе с ORM. А если бы понадобился поиск по постам, то пришлось бы в каждый такой запрос добавлять not is_posted. Также, после удаления поста(но не удаления файла), необходимо было бы подчищать таблицы связей(post_tags, post_comments и т.д.).

Пример комплексной таблицы file
CREATE TABLE file (
id INT PRIMARY KEY,
path VARCHAR(256) NOT NULL,
is_posted BOOL NOT NULL DEFAULT false,
thumbnail VARCHAR(256),
posted_by int REFERENCES(user)
)

Конкретно в этом случае, не лучше ли было бы иметь таблицу с постами, которая бы имела ссылку на запись в таблице с файлами?

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

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

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

Это называется "нормализация/денормализация".

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

Сентимент про "сложные запросы" и прочее проистекает от того, что по какой-то неведомой причине программисты пихают бизнес-логику в приложение, а DBA-и им это позволяют. Хотя интерфейсом общения с БД должны быть встроенные в базу функции.

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

UFO just landed and posted this here

Я для такого использовал pt-online-schema-change из Percona Toolkit
https://www.percona.com/software/database-tools/percona-toolkit

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

Но больно, да.

Дропнуть-добавить в нормализованной базе? Путем удаления-добавления таблицы с зависимостью по foreign key. Бизнес-логика при добавлении таблиц не страдает совсем, при удалении - сначала надо убрать зависимости из бизнес-логики. Где там будет даунтайм - не вполне понятно, если выдержать паузу на распространение изменения/устаревание кешей. Если добавление происходит с каким-то ненулевым объемом данных, то есть вероятность какой-то деградации на время добавления - тут, опять же, поможет DBA - как в качественно-количественной оценке рисков, так и в собственно работе.

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

Я скорее про процедуру миграции (ну вот например, хотим мы, не знаю, про юзера его предпочитаемый напиток, скажем, хранить). Соответственно, мы хотим добавить поле.
Но банальный ALTER TABLE для добавления поля - залочит таблицу на время добавления. Если она большая (например, это не юзеры, а какие-нить эвенты, входы на сайт или клики по баннерам) - это будет долго. И все прочие запросы к этой таблице будут ждать блокировку. Вот и даунтайм.
В противовес этому, если таблица денормализована (например, есть JSON-поле, в которое пихается всё дополнительное) - делать с ней вообще ничего не надо.
Расплачиваемся скоростью работы с таким полем и вознёй с целостностью, но тут уж куда деваться.

Мы, возможно, по-разному понимаем что такое нормализованная-денормализованная.

Нормализованная - это когда 3НФ и вот это все. Когда в таблице минимум информации, который используется для ВСЕХ записей, а все остальное, что может быть опционально (предпочитаемый напиток из примера выше), выносится в дополнительные таблицы с foreign key (на юзера из примера выше).

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

Повторяю, я _не_ топлю за добавление полей. Я против добавления полей просто потому, что так кому-то проще. И не стал бы его делать (кроме случая, когда изначально кривой дизайн с пропущенными ключевыми ОБЯЗАТЕЛЬНЫМИ ВЕЗДЕ полями, но это уже другая история).

Надеюсь, теперь мы на одной волне.

В случае с опциональными полями - да, тут абсолютно согласен.

Я тут наверное больше прикопался к тезису "дропнуть-добавить таблицу / поле в нормализованной БД несложно". Если нам надо добавить обязательное поле (например, мы теперь в таблице с событиями входа юзеров хотим всегда запоминать, с какого IP юзер зашёл) - нормализация не то чтобы поможет. Ну либо у нас будет хренова гора таблиц вида user_id, yet_another_field с зависимостями 1-к-1 на юзера и сборка всех данных по юзеру из этих таблиц превратится в многоуровневый join.
Хотя согласен, alter-ить таблицу юзеров в таком варианте не надо. Но выглядит как костыль, если честно.

Не было в моем исходном сообщении про "добавить поле" :)

Дропнуть-добавить разве не о том? Если таблицы дропать и добавлять, тогда конечно согласен, с этим проблем нет.

Hive? Но он, правда, не реляционный. И, конечно, не OLTP.

Ну да, делать OLTP на Hive - это что-то совсем странное.

Как вариант, чтобы разделить сущности (или, вернее, scope эти сущностей)?

Смотрите. Вот, к примеру, человек. Антон Дропбазаданко. У меня есть какой-то уникальный айди (предположим, uuid_v4 или, скажем, номер СНИЛСа (но насчет второго это не точно)).

uuid | name | date_of_birth (я-я)

uuid | worker_table_number | grade | title | salary (я-сотрудник)

uuid | size_of_feet | waist_size | height | army_specialization_id (я-военнообязанный)

ну и вишенкой на торте - гораздо удобнее выдавать GRANTs на такую БД для разных потребителей.

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

Для истории можно сделать отдельную таблицу.

Всё-таки исторические запросы нужны реже, чем актуальные данные.

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

А дальше ещё интереснее - у вас там могут быть не 1:1, а 1:0...1 и тут вы ещё и в объёме можете выиграть, потому что у вас основная таблица не будет лишнее место занимать.

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

Мягко говоря спорный пунктик... И уверен на хайлоаде с хотя бы 1к рпс там добавится своих аргументов

Для MySQL и его друзей есть Percona Toolkit, при помощи которого можно такой финт ушами (без даунтайма чтоб) проворачивать (в комменте выше отписывал). Не серебряная пуля, но может быть полезно.

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

И ещё один банальный пример технического ограничения: мне нужно прикрыть optimistic concurrency только одно поле в сущности. Или даже просто разделить поля с разным паттерном доступа (чтение/запись), чтобы это всё лучше чувствовало себя под нагрузкой.

Как насчёт разделения доступа? У вас таблица с данными о работниках. Там ФИО, рост, любимое блюдо, другая неконфиденциальная инфа, а также паспортные данные, адрес, телефон и прочее, уже чувствительное. Базой пользуются как отдел кадров, так и столовая. Если всё хранить в одной таблице, столовая будет, кроме любимого блюда работника, знать и его паспортные данные. Разделим таблицу на две, во вторую вынесем чувствительную инфу и ключи. Доступ ко второй таблице столовой не дадим. Таблицы по ключам свяжем 1 к 1. Профит. Чем плох такой сценарий?

GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;

можно так, джойны будут не нужны

Добавление колонки с nonnull значением по умолчанию в таблицу с миллиардами записей на pg до не помню какой версии вызывало остановку работы на полдня.

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

Для меня 1-1 можно применять только если вам нужно хранить в базе тяжелые картинки и лень прикручивать файлохранилище.

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

Для Вас, вернее ваших задач - не вопрос, но ведь не все только на "картинках" заканчивается.

Простенький пример: таблица клиентов с рисковыми коэфициентами. Для юр.лиц этих коэфициентов 100500, а для физиков - 100. Учитываем, что физиков ЗНАЧИТЕЛЬНО больше.

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

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

Ну, про собаку допустим. Есть собаки и есть будки. В каждую будку влазит максимум одна собака, у каждой собаки может быть максимум одна будка. Вы предлагаете эапихать это в одну таблицу СобакоБудка?

А если у человека есть адрес и рост, то вы называете сущность человекоАдресРост?

Нет, сущность будет просто собака, еще странные вопросы будут?

А это и не важно. Ваш оппонент о том, что может быть так: миллион юзеров, из них 999000 - физики, 1000 - юрики. При этом для физика заполняется 10 полей в таблице, для юрика - 100.
Дак вот в таком случае "поля-для-юрика" имеет смысл выпинать в отдельную таблицу и сделать 1-to-1 связь.
Экономия потому что.

тут он видимо просто 2 таблицы сделает, совсем отдельных

Все ок, кроме тех людей, у кого таблицы "всегда будут до 10тыс столбиков".

Полиморфизм.

Огонь пример)

Слабая связанность.

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

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

Другими словами, как ни сделай - всё равно новый программист найдёт к чему придраться))

В этом они очень похожи на сантехников и электриков.

А кто то делал?

Как я?

Сеньор - это тот кто знает где его юзать, а где нет.

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

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

А ты: я предвижу, что у вас все до выхода в прод развалится)

Можно подумать "сеньоры-помидоры" только спят и видят как бы так всё усложнить?

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

Как-то видел код, написанный индусами. Они аккуратно воспроизвели в нем все паттерны, описанные в пресловутой книге. Чуть ли не по порядку страниц шли.

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

Индустского не видел, бог миловал :) А вот немецкий видел, в моем личном рейтинге жести в плане "написать сложно простое" он лидирует.

Подержите моё пиво :)

К нам как-то приехал на поддержку кодец из далёкой Японии. Задача приложения — считать некий конфиг из базы и выдать через Rest API, как есть. Без обработки. Во-первых, трудолюбивые японцы написали много комментариев. На японском. В кодировке Big5 (или типа того). Исходник убил winxp коллеги при попытке открыть в Eclipse. А во-вторых, трудолюбивые японцы написали ручной менеджер потоков. Чем их не устроил родной джававский осталось загадкой. Возможно раскрытой в комментариях. Впрочем, зачем там вообще потоки, мы тоже не поняли. И в третьих, через 3 года выяснилось, что клиенты API ожидают отсортированные данные, а трудолюбивые японцы забыли добавить ORDER BY в SQL-запрос.

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

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

Они прекрасно подходят к главной задаче типичного корпоративного кодоваятеля - resume-driven development. Как иначе-то резюме украсить и расписать его с Махабхарату размером?

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

Нам надо хранить крохотные файлики, с чем легко справятся блобы в обычной БД? Не, так не пойдет! Заюзаем S3, потом опишем в резюме опыт работы с S3!

У нас простая и довольно легко реализуемая на монолите задачка, и можно железно гарантировать что количество данных лет за десять вряд-ли перевалит за один гигабайт? К черту монолит, монолит это сейчас не круто! Будем пилить все на микросервисах, с парой БД для каждого сервиса, одна из которых noSQL, с обменом по grpc, keycloak для авторизации, kafka, и кучей других страшных слов для резюме! Ну и само-собой распределенные транзакции, саги и это вот все!!! Красота ведь!

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

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

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

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

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

Следующая глава: как достать приватную переменную. Я думаю:"а это просто для идиотов разжевывают, я уж сам догадался!"

А там написано - нужно сделать паблик геттер и сеттер. Я думаю: идиоты чтоль? Я третий день программирование учу и то знаю способ проще: надо слово паблик вместо приват поставить.

Кстати, до сих пор не понял. Особенно зная, что в геттерах и сеттерах логику нельзя писать.

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

Но это если ЯП не поддерживает проперти, с пропертями все красивее и удобнее получается.

И откуда это правило, что логику в них нельзя писать?

Потому что код должен быть ожидаемым. Геттер - это геттер. Никто не ожидает, что в гетере будет логика.

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

Бизнес-логика - конечно нет, логика конвертации - да

В геттерах и сеттерах самое место логике установки и получения значения.
Например, для геттера — ленивая инициализация, для сеттера — контроль того, что значение лежит в допустимом диапазоне. Или конвертация значения из enum поля базы данных в enum-тип кода.
Например
enum ScheduleRecordType: string
{
    case DATE = 'date';
    case WEEKDAYS = 'weekdays';
}
...
class ScheduleRecord
{
    #[Column(
        type: Types::STRING, length: 16,
        columnDefinition="ENUM('date', 'weekdays')"
    )]
    private string $type;

    public getType: ScheduleRecordType
    {
        return ScheduleRecordType::from($this->$recordType);
    }

    public setType(ScheduleRecordType $scheduleRecordType): self
    {
        $this->type = $scheduleRecordType->value;
        return $this;
    }
}

Помимо перечисленных, у getter и setter есть ещё одно полезное свойство - их два, т.е. логика записи и чтения разделена. Что позволяет, например, нарулить переменную только для чтения.

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

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

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

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

Рекорды завезли уже. Это даже более правильное решение.

Это не одно и то-же, скорее совсем не то.

Конечно. Рекорды лучше. Вот ваш же пример

Класс может быть совсем без логики, с десятком полей и огромной простынкой геттеров/сеттеров.

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

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

Странное у вас противопоставление, "автобусы лучше клубники". Поэтому даже спорить не вижу смысла.

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

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

Тогда встаёт другой вопрос - зачем джава, если можно котлин.

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

Я третий день жаву читал. Не знал я ни про какие либы.

Про приватные переменные и геттеры сеттеры очень жирно троллите.

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

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

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

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

Мне кажется автор слишком часто меняет компании.

Хорошо быть бабочкой-однодневкой. Зачем готовить сани с лета? Зачем вообще - сани? Разве кроме лета что-то ещё бывает?

Если бабочкой-однодневкой хорошо быть, почему бы ей не быть?

Не надо завидовать. Надо брать пример.

Не получается. К примеру, мой стек plsql/plpgsql и c++. На plsql/plpgsql я свободно пишу в процедурном стиле, а на c++ - в ООП. Писать в процедурном на c++ мне почему-то физически больно (хотя когда-то мог..).

"Язык определяет сознание" (с)

Зачем тогда писать свои мысли, выдавая их за последнюю истину, зная что это не так?

Странно что минусуют. Реально, ты работаешь ради денег, а не для того что-бы сделать хорошо работодателю. Если стратегия "однодневки" позволяет зарабатывать больше, и не несет существенных негативных последствий, то почему бы и нет?

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

Сделано было хреново, но на эту реализацию тут-же навещали кучу функционала и уже просто так не тронешь.

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

Чем меньше знаний,тем проще кажутся задачи.

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

голову не стоит забивать тем, что не потребуется в обозримое время.

"— Пять минут: ближайшее будущее. Пятнадцать минут: отдалённое будущее!" (c)

Так и я о том, Зачем что-то изучать, Надо гордиться собственным невежеством.

Главное - вовремя кушать и правильно жевать. Психика будет как у крокодила.

Что вы несёте? Я знаю. Два способа решения задачи: один быстрый, дешёвый и простой, а второй сложный, долгий и забагованный. Я должен выбрать второй способ, потому что я типа умный?

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

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

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

))) Точно. Остальные способы - сложные, непонятные и следовательно неправильные. В чем нелогичность?

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

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

Это ж религия. Святое ООП. И отсюда неистовая религиозная ярость. Как он покусился на полиморфизм! ЕРЕТИК! Сжечь!

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

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

Так в этом и смысл! Код очевидный, простой к пониманию, плавает на поверхности. В нем нет ничего, что понятно только мне. Не к этому ли надо стремиться? Зачем усложнять? Вы меня хвалите или ругаете?

Чем меньше знаний, тем проще делаются задачи)))

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

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

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

А эт уже задача тимлида (или кто у вас есть) - управлять техническим долгом и выделять на это ресурсы. Причём совершенно из бизнес-соображений: "если не порефакторим счас, потратим на N% времени больше на изменение и на M% увеличим вероятность наплодить багов".

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

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


Если писать код сразу полностью отрефаченым, на чистовую и со всей преждевременной оптимизацией, то такой проект не доживёт до 10+лет — его просто закроют в первые полгода, потому что "куча дорогих спецов за кучу времени не смогли даже формочку логина сделать".


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

сеньор - это просто старший программист.

Когда приходит пора усложнять логику, делается рефакторинг. После, не раньше.

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

Но на практике лично у меня это применять пока не очень получается - примерно в 5% случаев я так делаю, а в 95% отключаю линтер. И из этих 95% примерно 25% это реально ситуации когда от рефакторинга в сторону уменьшения количества строк в функции станет хуже, но остальные 75% - просто нет лишнего времени на дальнейшее вылизывание кода.

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

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

Она показывает не вероятность ошибок, а количество for/if. От переноса части из них в другие функции их меньше не становится. Обычно это помогает улучшить читабельность кода, но - далеко не всегда. Например, когда у нас большой но однотипный if-elseif-elseif-elseif-…-else то с точки зрения цикломатической сложности код сложный, а на практике он обычно очень простой (и если его разделить на несколько функций он станет намного сложнее, что характерно). Что по "по началу", то я код пишу уже почти 35 лет :) и разбивать его на части отлично умею.

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

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

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

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

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

И не поймите неправильно, я не против линтеров как таковых - на данный момент у меня в текущем проекте включено 78 линтеров и отключено 15. И я подумываю конкретно эти 2 из 78 отключить. И причина в том, что то, что репортят остальные 76 в большинстве случаев приводит к исправлению кода, а не отключению линтера в конкретном месте кода - а для этих 2-х всё как раз наоборот, они почти всегда отключаются вместо правки кода.

Конечно, я могу ошибаться, и эти линтеры не стоит игнорировать. Но проблема в том, что время на задачу - не резиновое. Все очевидные и/или простые улучшения к моменту запуска этих линтеров уже выполнены, и выделенное на задачу время обычно уже полностью потрачено (просто потому, что вышеупомянутый процесс разработки подразумевает постоянный "фоновый" рефакторинг проекта вместо выделенных задач по рефакторингу, поэтому всё условно свободное время и так уже уходит на рефакторинг). А те изменения, которых требуют данные линтеры, редко когда получается сделать быстро (что можно было сделать быстро к этому моменту уже обычно сделано). И в результате складывается такой баланс: требуемые этими линтерами изменения делать долго, пользы от них будет меньше, чем от уже выполненного перед этим рефакторинга, а свободного времени на это уже не осталось. Ну т.е. пользы относительно мало, ресурса требует много, причём этот ресурс уже практически исчерпан. Вполне возможно, что при каких-то других условиях/командах/процессах от этих линтеров намного больше пользы, чем наблюдаю лично я на своих проектах, но у меня - пока так.

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

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

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

Ревью есть, конечно, как CI/CD. Изменения обычно небольшие (стараемся не делать таски крупнее чем 2-3 дня работы, но иногда всякое случается).

В прекоммит я запускать тесты/линтеры не вижу смысла - это мешает свободно коммитить по ходу дела (при мерже мы всё-равно делаем squash), замедляет и раздражает, а в master без прогона тестов и линтера на CI всё-равно смержить никто ничего не сможет.

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

Поспрашивал миддлов из своей команды - похоже что они замечания этих линтеров учитывают намного чаще, чем я: в 25-30% случаев. Причём, что интересно, мнения сильно разошлись в отношении того, какой из линтеров (по кол-ву строк или по асимптотической сложности) полезен а какой бесполезен. :) В любом случае, 25-30% это довольно много, так что да, линтеры эти отключать явно не стоит.

В C# ну очень удобно иметь интерфейс для одного класса (мне). Самый очевидный пример - моки. Конечно не для всех классов, но %3-5 у меня точно 1-1.

UFO just landed and posted this here

В Java класс без проблем замокировать. Кода нужно писать ровно одну аннотацию

Интерфейс нужен если его потом проксировать через ejb rmi, например

Неужели EJB еще кто-то использует?

Мне не понятно, почему код который постоянно дёргает if будет быстрее заранее выбранной имплементации.

Потому что динамический диспатчинг на уровне jvm не будет быстрее чем один if.

Виртуальный вызов дороже if

Причем на порядки раз. if быстрее раз в 10-30.

UFO just landed and posted this here

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

UFO just landed and posted this here

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

это из-за JIT может не видно

Виртуальные функции. В Java в явном виде их нет, но под капотом именно оно. Был где-то тест, где сравнивали с switch, последний работал ощутимо быстрее. Впрочем, зависит от того, в чём приложение проводит основное время. Где то и Optional имеет смысл выкидывать, потому что сравнение c null - чуть быстрее. Любой инструмент короче с умом применять нужно.

По одному интерфейсу для одного класса — это боль...

Моки просовывать жеж. Или тесты — тоже от лукавого?

Есть проблема мокнуть класс в тесте?

Невиртуальные методы мокать сложновато, да. Но в джаве возможность иметь таковые отсутствует, так что слив засчитываю.

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

Ну вот я начну... Сделали сначала всё на классах. Не без иерархий на 5+ элементов, но там доменная модель не самая простая. 1С короче пытаемся написать, в своей предметной области естественно. Потом выяснилось, что один класс тащит две (три и больше) обязанностей. Но описывает один объект реального мира, просто с разных сторон. Можно конечно распилить на мелкие классы и каждый в свою таблицу. С учётом полиморфных ссылок на него из других сущностей - как раз появится эта связка в БД 1:1. Пока вот пытаемся выделять мелкие интерфейсы (и класс будет реализовывать несколько таких), а где нужно - использовать их в качестве параметров. Ну и куча мест, где класс должен реализовать строго один интерфейс, если он (класс) достаточно мелкий. А глобально получим некоторый бардак - ненавижу в параметрах функций классы и интерфейсы вперемешку. Поэтому наверно будем реализовать эту вашу... бест практис..., вот

Я интерфейсы приемлю только там, где мы можем смигрировать с одной технологии на другую, сохранив контракт:

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

Для сервиса интерфейс никуда не уперся. Я его перериализовывать не буду, только модифицировать логику, на крайняк отэкстендюсь)

Две разные реализации под разные профили Spring?

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

Один код и на тесте и на проде.

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

Очень агрессивный стиль статьи и я честно ничего не понял, из того что хотел сказать автор.

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

  2. Ничего не понял про размер классов. Почему? Чем оно мешает то? Код читается как текст, выделяйте смысловые методы\абзацы\главы и не мучайте ни себя, ни пользователей кода.

  3. Связки в базе 1 к 1. - может кто и понял, но точно не я. Что это? Краткость не сестра таланта, вас обманывали.

  4. Про юнит тесты просто поток мыслей. Так и не понял, что первопричина гуана упомянутого, но в моих ощущениях - это автор.

Да, внизу стоит метка юмор, но я не увидел слова "лопата".

UPD: возможно пуши специально такие, чтобы на заблокированном экране не было видно код целиком? Я делаю допущение, я не в курсе.

Связки в базе 1 к 1.

Ну, тем, кто далёк от проектирования БД, эта терминология незнакома.

Между сущностями (которые в БД превращаются в таблицы) может быть три типа связи (по мощности): один-к-одному, один-ко-многим, многие-ко-многим.

Две таблицы, связанные как "один-к-одному", могут быть объединены в одну без нарушения целостности структуры БД. Такое разделение чаще всего носит технический характер (например, вынести в отдельную таблицу редкоиспользуемые поля большого размера).

Но автор статьи ничего из этого не пишет.

Я делаю дополнительные таблички один-к-одному и не вижу проблемы.

А автор видит, но не пишет, в чём именно.

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

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

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

Естественно можно юзать иногда. Но не злоупотреблять.

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

Простейший случай, когда подход 1to1 оправдан, это когда

  1. Данных в записи много (много колонок)

  2. Данные разнородные (относятся к разным бизнес-интересам)

  3. Данные пишутся один раз, почти не апдейтятся, но очень часто читаются

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

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

Видимо автор знает как хранят и обрабатывают данные БД.... А вот некоторые комментирующие не знают... :-)

Я в хранении не разбираюсь, и в чём таки секрет?

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

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

См. Денормализация

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

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

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

Хм, ну, допустим у меня есть таблица для данных с сенсоров, в которых есть определённые данные плюс данные GPS. По идее можно "нормализировать" данные GPS в отдельную таблицу и это будет связь 1:1. С одной стороны некрасиво, с другой я могу спокойно работать с данными геолокации не трогая остальные. С третьей ожидаемо прилетает запрос на другой тип сенсоров, для которых нужна другая таблица, потому-что они совершенно другие и данные GPS это единственное общее между двумя типами сенсоров. А у меня уже есть под это логическое разделение, да и для той задачи скорость чтения роли не играла, слишком мало данных для этого. Причём коллега до меня настолько ненавидел 1:1, что сделал огромный перекос в другую сторону - таблица с двумя полями - серийным номером сенсора и JSONом с данными.

На каждый новый тип сенсора вы будете таблицу добавлять?

Если уж так, то добавьте уж просто gps к каждой таблице с сенсорами. Зачем выносить отдельно?

Ну и другой вопрос, делается ли выборка по полМ в жсоне если нет, то и пусть лежат в жсоне.

А потом вспоминаем что сервера сейчас версионники, и хранить надо жирную строку записи. Т.е. порождение разных версий для разных транзакций, может увеличить используемый объем для версия записи.
Далее если запись не помещается на одной страницы, то с т.з. БД не большая разница читать одну запись с двух страниц или две записи тоже с двух страниц.
Далее некоторые сервера имеют лимит на размер записи в строке, что тоже выливается в 1-к-1.

Примеры с картинками не очень правильные, CLOB/BLOB всегда хранятся отдельно и в строке записи только ID CLOB/BLOB объекта.

Далее а почему все так на чтении зациклены? Две таблицы позволяют при изменении поля блокировать только одну. В следующем примере в вакууме разбитие на 1-к-1, позволяет избежать проблем с блокировками:
User: ФИО, e-mail и тд.
LoginCount: счетчик запросов от front.
Если две разные таблицы то тогда можно выполнять update не взирая на другие части программы. В случае если одна таблица то наличие двух транзакции например на увеличение счетчика и изменения e-mail, легко приведут к блокировке изменений. И хорошо если это буден не граф с deadlock.

а почему все так на чтении зациклены

Потому что самый распространённый паттерн в СУБД - чтение 70-90% и эти блокировки в 10% случаев там никакой роли в итоге не играют в производительности.

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

Ну в общем случае это же не так.
Если у вас 100+ полей, из которых реально используются в обычной жизни 10, а остальные используются для отчета раз в год — то очевидно, вам не нужна таблица со 100 полями.
Даже если они все укзатели(blob/longvarchar) все равно это по 32 бита на поле.

*сарказм*

это ж надо будет джоин писать. ты что. Или в бд будет +1 таблица. Зачем?

UFO just landed and posted this here

Ну не, это же медленно каждый раз в базу ходить. Надо проще — сделать селект всего, и у себя в приложеньке ссылочки в массивчики раскидать, gc потом приберет /s

Обратный вариант тоже бывает:

геораспределенное приложение и 1 (одна) база данных.

простая бизнес-операция превращается в 30 с небольшим реальных операций с базой данных, 30 * RTT, и пользователь немного удивляется быстродействию, находясь где-нибудь в Австралии (а БД, тем временем, немного во Франкурте).

Автор съел гуано с хибернейт на 1to1 и получил травму.

Возможно, кстати. Ответа от автора так и не увидел.

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

ну или можно при запросе всех 10 мильонов пользователей всегда джойнить вторую таблицу, а вдруг пользователь вип?

многие ко многим, кстати. Список пользователей на список привелегий.

Но мы же чисто технически можем добавить в первую таблицу почти 10 миллионов нулов, построить индекс

А вы пробовали? Просто в теории, например для postresql:

When a column is added with ADD COLUMN, all existing rows in the table are initialized with the column's default value (NULL if no DEFAULT clause is specified). If there is no DEFAULT clause, this is merely a metadata change and does not require any immediate update of the table's data; the added NULL values are supplied on readout, instead.

То есть добавление колонки в таблицу с 10 миллионами записей будет мгновенным, если вариант что значение по умолчанию будет NULL устраивает.
С индексом непонятно, но может быть создание индекса тоже будет мгновенным, если postgresql учтет факт что колонка вся нулевая. А потом обновление 8 строчек в таблице и индексирование этих 8 строчек тоже не должно занять много времени.

остается вопрос зачем нам в базе хранить 10 миллионов нуллов

остается вопрос зачем нам в базе хранить 10 миллионов нуллов

Так я же привел цитату из документации postgresql. NULL в таблице не хранятся, а просто подставляются в результат запроса при "SELECT".

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

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

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


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


Ничего из этого не является непреодолимым препятствием, но имхо один подход чуть проще другого.

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

Не очень ясна разница. В случае отдельной таблицы нужно при указании типа не забыть написать "NOT NULL", в случае "CONSTRAIT" нужно примерно тоже самое написать, разве что два раза: "IS NOT NULL" и "IS NULL", но тоже в одном месте. В общем разница особо не заметна.

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

Почему не выражается? Просто отдельный класс с этими полями,
в основном классе поле, ссылающийся на объект этого класс, помечается как nullable/optional и тому подобное. Плюс на поле навешивается аттрибут "flattern", чтобы ORM действовал как будто вип поля являются частью основного класса.

Почему не выражается? Просто отдельный класс с этими полями,
в основном классе поле, ссылающийся на объект этого класс, помечается как nullable/optional и тому подобное. Плюс на поле навешивается аттрибут "flattern", чтобы ORM действовал как будто вип поля являются частью основного класса.

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


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

их можно не хранить. напрмиер mssql умеет sparse columns

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

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

Настройки на уровне приложения\пуша или телефона?

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

Настройки на уровне приложения\пуша или телефона?

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

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

Тогда да, непонятно, почему этим не пользуются.

Я не Android-разработчик, но да именно так и есть. Раз из compatibility layer, два из главного туториала от Google, три -- узнаем, что ввели это в аж в Android 5.0, API 21. И нет, отговорки про Compatibility ещё более старых версий тут не канают, потому что там где надо, лишние 2-3 строчки if-кода должны быть написаны ради privacy и security пользователя.

Offtopic: А секьюрити после 22-го года буквально физическое: отобрали телефон и без пыток смотрим на залоченный экран, пока есть связь. Высветилось что-то не то - и начинаются пытки вопросы.

Есть. Но сколько пользователей ими воспользуются? И сколько про них знают? А владельцу сервиса потом бодаться с мошенниками. Проще сделать длинный текст.

На моём смартфоне это была настройка по умолчанию.

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

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

Там вроде еще дальше пошли немного другая причина. Защита отображения кода в пуше без авторизации на экране ожидания.

У сбербанка я помню эсэмэски с кодом подтверждения, которые были из трёх частей, чтобы наверняка (?) -_-'

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

Добавьте, пожалуйста, мыслей про машинное обучение и усугубление выше названного.

Если полностью внедрить технологии типа GPT4 – хорошо интегрировать с IDE и различными API, то ценность обычных программистов может постепенно падать, как это произошло с разработкой сайтов. Как минимум из-за того что 1) отдельные виды приложений можно будет просто сгенерить, как минимум работающий каркас 2) автоматизация широкого класса задач, на которые раньше тратили заметное время – как раз тех задач, для которых не требуется работа мозга. За освобожденное с помощью "ИИ" время никто не будет платить, значит для получения тех же денег придется делать что-то другое. Какие специалисты после этого будут в цене?

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

Можно только сесть в кресло американских горок и наслаждаться скоростью изменения мира вокруг нас, при этом пытаться спорить про то, нужно ли иметь фундаментальные знания типа математики/алгоритмов.

Я вас понял. Благо я не так категоричен.

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

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

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

Но да, усложняться ЧТО-ТО будет, ГДЕ-ТО будут выше требования. Но это не везде и не всюду.

Бизнес же, который говорит что уже готов заменить дизайнер, программистов, снизить им з/п, потому что есть великий и могучий <вставить нужный сервис с нейронкой>, не знает о чём говорит и мечтает о том, чтобы сэкономить здесь и сейчас, а не развить продукт. С такими просто не стоит работать, как по мне. Долгосрочного видения я здесь не вижу.

Постепенно люди, и мы, создатели ИТ-продуктов, поймём как использовал все эти нейронки как ЕЩЁ ОДИН инструмент. К примеру, чтобы делать код безопасней (взломы и прочее никуда не ушли), как быстрее находить информацию, проверять её на соответствии тем или иным критериям, ну и т.п.

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

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

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

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

Удивительно, что автора продержали весь испытательный срок, а не выкинули нахер в первую неделю. Я бы выкинул точно

Все просто))) Если б они уволили я получил 4 зарплаты за месяц. Совсем обидно. А так всего 2 зарплаты в месяц) Завидуйте молча.

Не понял, почему 4 зарплаты? Типа, при увольнении вам обязаны были бы выплатить 3 зарплаты сверху? Но ведь испытательный срок на то и испытательный срок, что на нем можно расстаться с сотрудником без всяких условий.

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

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

Хотелось бы увидеть статью от того, кто придёт на проект после вас. Жаль, что такое совпадение маловероятно.

Ну он скажет, что код писал какой-то джун. Примитивный круд, никаких тебе реактивщины и функциональщины. Ну всё работает, да. Но всё ужасно просто.

Я видел его код. Первым делом он поменял версию жавы. Потом какие то валидаторы на поля навешивал.

Но тут приходит корпоративный прогер

Вам не надоело "побеждать кровавый энтерпрайз" на избитых детских примерах типа "Cat extends Animal?

Придите уже хоть раз с реальной фактурой. Вот, например, была у вас полиморфная стретегия на десяток классов - вы "ускорили" код, объединив все в один условный оператор на 1к строк. И стало классно.

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

Кстати, странно, что вы Spring не "отменяете" и не "побеждаете". Там под капотом сплошные полиморфизмы и паттерны. Рекомендую книжку одноименную почитать. И именно это делает Spring наиболее популярной платформой на JVM с околонулевым порогом входа.

Была у нас полиморфная стратегия на 12 классов. каждый класс мапился на свою репу, которая мапилась на энтитю без полей, которая 1-1 к одному мапилась на родительскую. Все это можно было переделать, findEntityByType(Type), но я не сделал.. Удалить 12 таблиц, удалить 12 классов, 12 реп, 12 сервисов, но я не сделал, потому что я нетоксичный корпопрогер.

В общем аргументация уровня "меня в Саратове в 98 году гопнули на 100 рублей, с тех пор ненавижу Саратов". Где-то когда-то видел корявый кейс применения полиморфизма - значит полиморфизм не нужен.

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

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

Я бы ещё добавил уникальную нумерацию сообщений об ошибках, чтобы 1. Пользователи чётко знали ошибки в лицо, а не по правилу «что-то-пошло-не-так+GUID» на одну и ту же ошибку, которую ещё надо найти в логах и 2. Чтобы однозначно связать источник ошибки с кодом. (А если в проекте несколько библиотек, то к номеру ошибки ещё добавляется имя библиотеки), чтобы на крайний случай обратиться к разработчику напрямую. У нас уже все знают, что делать, когда номер ошибки в расчётах - 172. Особенно при тестах помогает разным разработчикам, что они знают как реагировать самим и как понимать ошибки других: эй, у тебя ошибка 45 что означает? Иногда в исходниках прописываю, что проверить дополнительно. Бывает проходит год, добавляется ещё какой-нибудь параметр в модель и сразу сыпятся ошибки. Читаешь комменты к ошибкам и понимаешь, что ещё надо сделать для нового параметра. Потом благополучно забываешь про это до следующего года. Вот так система сама себя и поддерживает. )))

Class Animal{String type, voice()

{ if (type==dog) -> gav, elseif(type==cat)->myau else->not} }

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

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

когда появятся такие требования то тогда и только тогда стоит усложнить структуру.

До до до...
Посмотрим какие у нас варианты
1) Удалить Anilmal полностью, разделить логику между наследниками = бегать по всей кодой базе и искать где конкретно я должен подменять Животное из "суперкласса" на конкретную Собаку/Кошку/Змею, очень удобно и точно ничего не сломается 146% гарантии (ровно до того момента пока не выясниться, что ваше суперживотное, давно ушедший джун, заставил сначала ползать по трубам потому что Змея же, а потом еще и мяукать потому что тоже умеет)

2) Не удалять Animal потому что чревато непредсказуемыми последствиями, выстроить новую систему типов рядом = поддерживать две кодовые базы для работы зоопарка.


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

Сделайте Animal абстрактным и пока везде не замените - оно не скомпилируется.

А то можно написать IAnimal, IVoiceable, IMoveable, IWhateverIImaginedWillBeNecessaryable, а потом окажется, что вы теперь вместо зоопарка делаете ферму и нужны растения. А от животных остаётся только собака, чтоб охранять.

Это как решит проблему неправильного использования?

А еще можно много чего додумать, но я остаюсь в рамках пр веденного примера.

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

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

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

Меняем "тип" объекта, проползаем через трубу, снова меняем "тип" обекта и уже мяукаем.
Ни один компилятор вас от такого поведения не защитит. От подобной "мутации" как раз вас защитит система наследования животных.

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

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

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

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

Дак система наследования, всё верно. Но если у нас попросили сделать котика, зачем сразу же делать пяток интерфейсов и иерархию на 3-4 уровня? Может быть лучше просто сделать котика? Конечно, потом могут попросить пёсика, змейку и ещё кого. А могут и не попросить, мы ж не Ванга, чтобы это предвидеть.

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

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

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

Ну или тебя говорят, ништяк! Проект зашёл! Заведи всех остальных 40000 животных, табличку животных и их звуков мы скинем. И ты такой: как хорошо, что код расширяем, надо просто сделать 40000 классов и 40000 таблиц в БД.

А я такой ништяк, просто суну их названия звуки в базу.

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

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

А куда 40000 ifов денутся?

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

У нас метод строку возвращает по типу животного. Вы б сами, как сделали?)))) наверное базу б положили?

Если единственная решаемая задача — выдать строку на основании другой строки, то хватит и ассоциативного массива или Map, в зависимости от языка. Заодно и работать будет быстрее, чем ваш код.

Заведи всех остальных 40000 животных, табличку животных и их звуков мы
скинем. И ты такой: как хорошо, что код расширяем, надо просто сделать
40000 классов и 40000 таблиц в БД.

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

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

Я так понимаю если бы вам сказали спроектировать систему учета кадров, вы бы на каждого новогу сотрудника новый класс и таблицу заводили? Или новую ветку в "универсальном классе сотрудник?

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

Плак плак... зумер не осилил полиморфизм... зумер крушииить!!!

FindvoiceInDbByAnimalType('dog') посмотрел?

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

Так про полиморфизм был разговор в рамках другой задачи.

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

Предварительная оптимизация вроде это называется? Вот когда потом это произойдет, тогда и отрефакторите.

Ну и кстати, вы не чуете тонкой грани полиморфизма. Полиморфизм нужен не когда один сложный метод, а когда у каждого класса, есть много методов и каждый со своим поведением, а иногда родительским. ну типа voice(), run(), sex(), eat(), а иногда уникальными методами fly() и собственными переменными int speed, maxflyheight. Тогда полиморфизм годен и необходим. А один сложный метод никто не мешает побить на несколько для начала или на крайняк вынести в VoiceHelper

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

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

Видели. Вот такой например код из прод кодовой базы:


impl Responder for UpdateResponse {
    type Body = BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        match self {
            UpdateResponse::UpdateSuccess => HttpResponse::Ok().finish(),
            UpdateResponse::UpdateIgnored => HttpResponse::Ok().json("Update ignored"),
            UpdateResponse::UpdateNeedsFullData => HttpResponse::new(StatusCode::IM_A_TEAPOT),
            UpdateResponse::AlreadyUpdating => HttpResponse::new(StatusCode::IM_A_TEAPOT),
        }
    }
}

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


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

У меня не было лабараторных работ. Весь опыт коммерческий обычно в больших и очень больших проектах.

"категоричный эмоциональный высер" - самая популярная статья за месяц))))

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

Э-эффективность. Это уровень надсеньора, не ваш уровень, вам не понять) Это как поэзия, только в программировании. Тут чуять надо.

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

Э-эффективность. Это уровень надсеньора, не ваш уровень, вам не понять)
Это как поэзия, только в программировании. Тут чуять надо.

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

Паттерны всё могут выучить(я не смог)

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

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

По статья я не понял, что вы тоже это чевствуете.

Вот когда потом это произойдет, тогда и отрефакторите.

Это так не работает. Рефакторинг это не только красивое слово, но и очень противное дело.

  1. Когда вы захотите отрефакторить, вам понадобятся уже готовые тесты, чтобы убедиться, что нет деградации.

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

  3. А интерфейсов вы не завезли, поэтому, собственно, и нужен рефакторинг. Собака съела свой хвост. Рефакторинг отменяется, всё переписываем с нуля, но, видимо, уже не вы.

Как у вас 2п появился? Каким образом без интерфейсов вы утоните в тестах?

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

Оверинжениринг это плохо. Но и недоинжениринг это тоже плохо. Когда надо двигаться вперёд а у тебя в коде макароны и нет тестов, то скорость катастрофически падает.

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

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

Неиспользуемых? Так удалите и дело с концом.

По моему опыту выпиливать не так уж и сложно.

У вас кстати ревью на проекте есть? Или тоже потеря времени?

Чаще всего с подобным кодом проблема в том что он неиспользуется не на 100%, а так, где-то на 99%, и вот из-за этого жалкого одного процента горы воняющего кода отравляют весь проект. Сам такое неоднократно видел. Любая попытка рефакторинга выливается в то, что либо отчаянный разработчик взявшийся за то дело, на полпути перегорает и берется за что-то более приятное, либо ему ставят более срочную задачу, либо он все-же проходит весь путь до конца и выдает в итоге громадный МР, который никто не хочет ревьювить и брать на себя ответственность за возможные проблемы (а они будут 100%), в итоге МР висит с месяц, становится неактуальным и про него все благополучно забывают, а где-либо через полгода автор его удаляет, что-бы не мозолил глаза и не портил настроение.

У нас видимо разное понимание "не используется".

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

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

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

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

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

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

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

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

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

Главное препятствие на пути рефакторинга малыми шагами это время ревью. Если на MR забивают в течении недели — то хочется делать изменения большими пачками.

Это да.

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

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

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

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

Вы правы в том что делать надо маленькими шагами. К сожалению это не всегда возможно. Как пример один из последних рефакторингов на прошлой работе - там непризнанные гении велосипедостроения такого наворотили.. Что-бы перейти с самописного ORM (уж не знаю какого художника так вдохновила идея написания своего ORM и почему его не остановили коллеги) малыми шагами не отделаешься, это очень объемная работа с массой мест где можно накосячить, хоть переход делался не одним мной и по частям, но части эти делались очень крупными мазками, по другому просто невозможно было. И такое бывало в практике не один раз. Хотя, я согласен, что в идеале надо рефачить малыми итерациями. К сожалению так не всегда возможно в старом и запущенном легаси.

Что-бы перейти с самописного ORM (уж не знаю какого художника так вдохновила идея написания своего ORM и почему его не остановили коллеги) малыми шагами не отделаешься


Философский вопрос, можно ли такое крупное изменения назвать рефакторингом?

Если ставить цель найти маленькие части, то они находятся. Хотя если вам выделили на этот рефакторинг кучу времени, то зачем разбивать?

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

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

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

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

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

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

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

Второй критерий для меня, я делаю это без постановки задачи. То есть размазываю по текущей нагрузке. Фиксишь баг, нашёл что-то не связанное когда код читал, быстренько пофиксил. Или делаешь новую фичу, посмотрел на код порефакторил, а потом добавляешь новую фичу.

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

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

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

Всё хорошо в меру. Был у нас разработчик, который обожал разрезать имплементацию на минимальные кусочки, кусочки разложить по иерархиям, снабдить интерфейсами и припудрить Parameter Object, потому что иначе контекст вычислений не передать... И каждый раз, чтобы понять по логу что конкретно пошло не так, нужно было в голове всё это собирать. Потому что глядя на код программы - невозможно понять, при каких условиях какой кусочек кода будет использован. Там ещё и карта Command была до кучи, со строковыми ключами, если правильно помню.
Я вот последнее время вообще начал мечтать, чтобы IDEA научилась на место вызова функции рендерить её код, чтобы можно было посмотреть весь контекст выполнения не прыгая по десяти файлам. Ну вроде как рефакторинг Extract method, только с точностью до наоборот, и только чтобы посмотреть. А в случае виртуальной функции - да, рендерить вот тот switch по необходимости

IDEA в режиме отладки позволяет пошагово ходить везде, даже внутри библиотек. Не оно?

Ходить и смотреть глазами я и сам могу :). Но время от времени хочется (и нужно) почитать "роман", особенно при анализе PR / MR, чтобы не держать контекст в голове

Для таких вещей существует рефакторинг:)

UFO just landed and posted this here

Добавил + за упоминание Antlr. Офигенная штука, не раз выручала, причём как раз в похожих ситуациях, когда много нагенерированного текста.

Вопрос - какой sql и где брали грамматику для antlr?

UFO just landed and posted this here
UFO just landed and posted this here

Слава богу, что я такой не один )))

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

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

UFO just landed and posted this here
  1. Интерфейсы нужны для инверсии зависимостей.

    Инверсия зависимостей нужна для изменения направления зависимостей.

    Направление зависимостей изменяется в сторону бизнес-логики.

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

  1. Интерфейсы чаще всего могут иметь минимум две реализации - сама реализация и мок для тестов. Благодаря соблюдению принципу DIP код очень просто покрывать тестами, в том числе.
    Есть ещё noop реализации, как один из способов решения проблем с NPE.

Автору и сторонникам данной статьи настоятельно рекомендую ознакомиться с работами Дяди Боба и другими.

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

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

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

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

Но есть и другая сторона.

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

Коротко резюмируя: вы правы отчасти, но смотря что писать

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

Интерфейсы чаще всего могут иметь минимум две реализации - сама реализация и мок для тестов - АХАХА! Если есть тесты!))))

Вы не пишите сложные инструменты, а обычную бизнес логику

Запустите async-profiler для spring приложения. Откройте flame graph и увидьте, что для того чтобы выполнить CRUD запрос обычный в rest, то где-то 100 прокси в виде реализаций фильтров и прочего сначала обрабатывают у себя, и только потом сбрасывают вам в контроллер на обработку. Тоже самое после return из контроллера. Т.е. вы увидите в основном только "кишки" фреймворка и чуть своего.

В спринге многие вещи даются легко потому что происходит IoC - все как раз делается для нас за кулисами с теми же интерфейсами, изоляцией разных слоев, паттернами и стратегиями. Кстати там еще довольно таки мало костылей :)

Так а зачем вам тесты? Я так понял они только в гуано проектах… Вы же не хотите работать с гуано проектом? Значит и тестов быть не должно

Если у тебя другая позиция, то напиши какая в качестве ответа на комментарий. Мне интересно почитать аргументы против. И подискутировать.
Зачем за иную точку зрения сразу минусовать в карму?!

Так это же думать надо. А так щелкнул мышкой - и типа возразил.

UFO just landed and posted this here

Иногда работало. Правда не в настолько бурных темах.

Интерфейсы нужны для инверсии зависимостей

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

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

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

Я вот честно не понимаю почему в Сбере
Пишут тесты на классы созданные mapstruct????

Авторы данной библиотеки сами уже написали тесты, прежде чем выложить в общий доступ

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

С таким успехом можно писать тесты и на то же Spring

Почему не писать тесты на Service класс где это конвертор используется и делать все скоупе

Ааааа потому не понимают разницы между интеграционным и юнит

Ведь намного проще в тесте создать объект мапстракта и тестировать его! ((((

Я вот честно не понимаю почему в Сбере
Пишут тесты на классы созданные mapstruct????

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

Пытался пользоваться mapstruct без тестов и были проблемы. Тестами покрываются не код самого mapstruct, а аннотации маппинга (и их отсутствие). Напортачить там время от времени легко.

В итоге, сейчас в Котлине mapstruct вообще не пользуюсь -- проще самому написать.

Вы видели бы тесты

Создаётся ентити в поле ложится строка
И потом в дто проверяется, что там лежит эта же строка

О да! Особенно в связке с Lombok. Не далее как вчера - перестало собираться приложение. Ломбок не ломбочит, билдеры не найдены, 100 однотипных ошибок maven. Внятного сообщения об ошибке - нет. Стал разбираться, что менял. Оказалось - прибил одно поле, которое было явно прописано в mapper-е. В нём самом удалить забыл, это же просто аннотация со строковыми полями. MapStruct на этом предположительно молча падал, после чего не запускался следующий annotation processor. Нашёл только в логе ребилда в IDEA, по паттерну Unknown property .+ in result type, а там и догадался. Минус час жизни.

Можно заэксклудить пакет с мапстрактом

а почему не убрать просто эти интерфейсы или абстрактные классы из покрытия? они как правило имеют суффикс Mapper и можно сделать маску по таким файлам.

Я вот честно не понимаю почему в Сбере
Пишут тесты на классы созданные mapstruct?

Не знаю как там с Сбер, но:


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


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

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

ну это так) вспомнилось просто)

МапСтракт зло, одно дело ентити в дто положить
Другое дело логика

Мне только некоторое время назад начали приходить ответы по mapstruct на Stack overflow , которые я спрашивал в 2017

На всех моих крупных проектов кроме Сбера, конвертеры были написаны вручную

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

У нас на проекте используется mapstruct. И вот типовой пример: один из микросервисов не возвращает атрибут. Чтобы понять - че за фигня - надо раскручивать логи нескольких микросервисов. Время тратится будь здоров. Оказывается что сервис добавления этого атрибута при маппинге данных из соседней системы - не смапился. Потому что у нас это атрибут (условно) userRole , а в смежной системы userTeamRole. Прогер поленился написать тест, чтобы проверить что все мапится корректно и он ничего не упустил. Вы скажите - ну так прогер виноват, он не учёл и не добавил аннотацию. Так и юнит тесты пишем чтобы как раз уменьшить число таких косяков

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

Уважаемый автор, а что это была за компания ? Мне чисто для себя, чтобы обходить ее стороной)

UFO just landed and posted this here

К слову о крудах и сеньорах - вот что получил при попытке залогиниться на Хабр чтобы поставить лайк статье :)

На Хабре хотя бы незаметно происходит аутентификация, в отличие от всяких сервисов Яндекса, по нескольку раз редиректящих на всякие sso.passport и прочие sso.

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

UFO just landed and posted this here

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

yandex.net
yandex.ru
yandex.com
yandex.com.tr
yandex.ua
ya.ru

список можно продолжать долго, но думаю суть понятна.

UFO just landed and posted this here

Не Logined, а LoggedIn, если уж пошла речь об именах

То есть gav и myau вас не так смущают? :)

Не знаю, я видимо из "неправильных".

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

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

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

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

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

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

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

Как раз для решения сеньорских задач, надо понимать весь код целиком.

И когда код побит на миллион маленьких кусков - это сложно. Поэтому весь код должен быть структурирован, у него должен быть структура.

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

Количество кусков и структурирование - это перпендикулярные параметры. Правильно выбранные слои абстракции, позволят вам описать систему в пару предложений текста и знание всей кодовой базы вам не нужно. А вот если для внесения изменения, вам нужно знать код целиком, то вы приехали. На приложении в 5к+ строк кода это уже будет трудно. На приложениях 100к+ строк - это будет ад, в котором любой улучшение или исправление будет вноситься человеко-неделями разработки и тестов.

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

Если для логически небольшого изменения надо понимать всю систему - это ад. Могу припомнить случай, когда нужно было в одной e-commerce системе добавить товару скидку в 5%. Я не смог это сделать корректно за месяц, весь этот месяц я разбирался в коде и пытался поместить его в голову целиком (система большая). Да, и там нет полиморфизма вообще, это go.

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

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

Да, и там нет полиморфизма вообще, это go.

Есть в go полиморфизм, не вводите в заблуждение. И проблемы в вашей системе вовсе не связаны с выбранным языком.

Я не говорил что в go нет полиморфизма, я сказал что ТАМ нет полиморфизма, и это go, через зяпятую. Наверно ввело в заблуждение. Просто на go не особо (видимо) любят делать много абстракций.

нет полиморфизма вообще, это go

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

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

Я согласен, может вы даже лучше выражаете мысль, чем я.

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

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

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

Вы в статье действительно не особо объективно утверждаете что не так с приведенными примерами.

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

«Код: 123 456».

Расскажите об этом @Alfabank! Коды из их пушей/СМС у меня на телефоне (на разных телефонах - за много лет разные ОС и их версий, разные производители) тупо не парсятся и не подставляются в форму подтверждения. И, да, код нужно указывать в начале текста пуша, иначе его не прочесть глазами и не ввести, пока пуш висит на экране (точнее, в некоторых ОС можно "нажать на галочку", и вывести больше текста из пуша, это немного спасает, но все же). И это про пуши, а про СМС все еще грустнее, там уведомление про сообщение может и поскромнее выводиться - у общем, указывайте код в начале текста!

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

В Беларуси альфа шлет код в формате xxxxxx; <какой то текст>.

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

В Эппл решили эту проблему изящнее с момента появления FaceID: если на телефон смотрит не владелец, то сообщение отображается, но текст скрыт. К тому же они добавили ещё одну строчку в предпросмотре, раз уж теперь они могут определить, кто именно смотрит и коды стали влезать. Кроме того, в подсказки клавиатуры iOS давно подставляется код из СМС, которое пришло в момент набора, поэтому сообщение вообще не нужно открывать, достаточно кликнуть на код в подсказке.

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

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

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

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

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

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

Вы удивитесь, но андроид-фоны точно так же можно настроить не показывать текст уведомлений на заблокированном экране

Только из-за отсутствия фейс-айди как раз никакой изящности и нет. На айфоне ты видишь уведомления практически сразу почти всегда именно из-за него. А сторонний человек не видит.

Распознавание по лицу есть и у андродтелефонов уже много лет... И вообще это впервые появилось не на айфонах)

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

А мне кажется, что фраза "Никому не сообщайте этот код" должна быть до самого кода

До Zen of Python пока далеко, но тенденция обнадеживает.

Опять таблицы! Вообще хорошим тоном считается обращаться к базе только через view! Делать селект из таблицы напрямую это как использовать GOTO для реализации цикла.

Соответственно, при связи 1 к 1 надо просто создать VIEW эмулирующий одну таблицу и работать только с ним . Что там под капотом и почему CRUD логику интересовать не должно.

Все таки таблица это очень низкоуровневый объект. По хорошему надо знать на каком физическом носителе она находится, параметры файловой системы. Тогда, просто подгоняя параметры, можно увеличить производительность в 2-3 раза. Посмотрите на все параметры создания таблицы в MySQL https://dev.mysql.com/doc/refman/8.0/en/create-table.html или ORACLE https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/CREATE-TABLE.html. Реально JAVA- Сениор не должен определять какой нибудь DEFERRED SEGMENT CREATION. Дальше VIEW он ходить не должен, это не его зона ответственности.

Если производительность не интересует - то да... :-)

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

В общем, не стоит из крайности в крайности ударяться.

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

Чем код проще, чем легче его чинить и улучшать. Это аксиома вроде? Нет?

Потому что не любой код надо чинить и улучшать.

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

Я вот как раз работаю над одним проектом, где всё просто в лоб делается. На базовую клиент-серверную дробилку данных навешан максимально простой код. И пока логика была линейна и функцонала мало - всё было очень просто. Но оказалось, что в таблицах легко вбивается чушь и код это ест, работает, но выдаёт чушь. Там где уже надо было делать код на классы работает 3 разных метода с копипастом на 80%+, каждый состоит из пары десятков условий, которые очень сильно разносятся между собой и находятся в разных областях знания о проекте. В каждом методе куча условий на входе, которые тихо прекращают выполнение if (bool) return;
Справедливости ради, читать там в большинстве случаев легко. Только в каждом методе может потребоваться знание о любой части проекта. А изменение может поменять логику непредсказуемым образом, но отработать без ошибок.

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

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

С добавлением функционала так же просто, пока он не задевает логику старого. А вот если затрагивает, то опять как с расширением, только сложнее. Проще переписать ту часть с 0, хотя бы по частям.

Пока хуже было только с колбеками.

Списибо, что поделились опытом.

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

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

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

> С добавлением функционала так же просто, пока он не задевает логику старого.

Другой важный момент который я вынес (open-closed-principle), что не надо менять внутреннее API для новых фич. Просто добавляйте новые методы и документацияю к ним. Два похожих метода это нормально, три тоже. Перед добавлением четвёртого можно подумать о рефакторинге, но если нет времени то добавить и четвёртый, пятый, десятый. Если вы не меняёте существующий код, вы не сломаете старое добавляя новое, а если что-то чините, то это часто сужает количество кода который нужно перепроверять. Тоже получился мешанина, но уже намного более контролируемая.

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

Как раз добавление кучи однотипного кода приводит к проблеме. Условно, тебе говорят - почини вот ту фиговину, чтобы когда А, происходило B... А там 10 методов, отличающихся только условиями, причём условия из данных в другом пространстве. И ты не сможешь починить корректно без дополнительной инфы, да и потом надо будет искать 1 требуемый из 10.

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

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

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

Разделение - это не отсутствие зависимостей, это однонаправленные зависимости, которые вы контролируете. Ваш бизнес зависит от АПИ и ваш модуль зависит от АПИ. Пока вы поддерживает обратную совместимость АПИ (добавление нового метода не ломает её), добавление новых фич должно идти хорошо. Конечно при добавлении новых фич вы должны добавлять их и не ворошить внутрянку модуля. То есть если вы для новой фичи поменяли какой-то приватный метод, который используется везде то старое может сломаться, а мы этого не хотим.

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

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


Стоит тег юмор, но звучит как истина, браво

Anonym→Logined→Phone_Confirmed

Два паунда шримпов, пожалуйста.

Сеньор пришедший через собеседование с задачками с литкоде, и посаженный на мидловые таски, те же круды, рано или поздно затоскует. И тут уже дело времени, когда он начнет дурковать, вкручивая все эти алгоритмы Флойда‑Уоршелла в методы проверки авторизации. В этой ситуации, не сильно отягощенные математикой сеньоры, будут более безобидны или даже полезны. Они займутся UX, code style и т.п.
Выход, это наверно пэт проект, куда можно сливать всю эту оверактивность без вреда для окружающих и когнитивного диссонанса по основному месту работы.

Создать хороший КРУД сложно. И там нужны хорошие сеньоры.

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

Но когда я вижу карбоновый капот на логане....

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

Знакомая боль. Но как известно, все люди с запредельным самомнением рано или поздно становятся синьорами.

Я просто оставлю это здесь.
Awesome yet simple and pragmatic PHP library performs an addition of two numbers.


Спойлер

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


Только представьте, чтобы выполнить простейшую операцию, сложение двух чисел, нашим предкам приходилось писать 1+1. Реально.


К счастью, сейчас у нас есть OOP. SimplePHPEasyPlus позволяет вам производить сложение гораздо более современным способом, используя всю мощь ООП. Это простая, быстрая и гибкая библиотека, код которой полностью покрыт тестами!
Чтобы сложить 1 и 1, всё что вам нужно, это:


use SimplePHPEasyPlus\Number\NumberCollection;
use SimplePHPEasyPlus\Number\SimpleNumber;
use SimplePHPEasyPlus\Number\CollectionItemNumberProxy;
use SimplePHPEasyPlus\Parser\SimpleNumberStringParser;
use SimplePHPEasyPlus\Iterator\CallbackIterator;
use SimplePHPEasyPlus\Operator\AdditionOperator;
use SimplePHPEasyPlus\Operation\ArithmeticOperation;
use SimplePHPEasyPlus\Operation\OperationStream;
use SimplePHPEasyPlus\Engine;
use SimplePHPEasyPlus\Calcul\Calcul;
use SimplePHPEasyPlus\Calcul\CalculRunner;

$numberCollection = new NumberCollection();

$numberParser = new SimpleNumberStringParser();

$firstParsedNumber = $numberParser->parse('1');
$firstNumber = new SimpleNumber($firstParsedNumber);
$firstNumberProxy = new CollectionItemNumberProxy($firstNumber);

$numberCollection->add($firstNumberProxy);

$secondParsedNumber = $numberParser->parse('1');
$secondNumber = new SimpleNumber($secondParsedNumber);
$secondNumberProxy = new CollectionItemNumberProxy($secondNumber);

$numberCollection->add($secondNumberProxy);

$addition = new AdditionOperator('SimplePHPEasyPlus\Number\SimpleNumber');

$operation = new ArithmeticOperation($addition);

$engine = new Engine($operation);

$calcul = new Calcul($engine, $numberCollection);

$runner = new CalculRunner();

$runner->run($calcul);

$result = $calcul->getResult();
$numericResult = $result->getValue(); // 2

"Костюм" Райкина в исполнении работников ИТ сферы.

"Any intelligent fool can make things bigger, more complex, and more violent. It takes a touch of genius and a lot of courage to move in the opposite direction." - Albert Einstein

Поискал цитату, нашлось несколько упоминаний, что автор — E. F. Schumacher. Испытал любопытство, кто в действительности автор )

Тесты не пишем — времени нет.

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

Да та же Java, но имеет ли это существенное значение? Всё, что потенциально может перерасти PetClinic, будь оно хоть на Brainfuck написано, должно иметь какие-никакие тесты, покрывающие главные сценарии использования ПО.

Кому должны? Из 7 проектов на 5 не было тестов вообще. И все это очень крупные проекты от 2 до20 миллионов пользователей.

Не хочу вдаваться в полемику. Назовём это ошибкой выжившего (хоть в моём, хоть в вашем случае). Как знать, может быть у всех этих 5-ти проектов были грамотные лиды, которые были в состоянии держать код в чистоте длительное время и отлавливали баги ещё до того, как они доберутя до прода. Мой опыт диаметрально противоположный. Люди ведь не роботы. Это Jenkins может гонять тесты за тестами после каждого коммита без устали, а у программиста Васи, каким бы гениальным он ни был, рано или поздно концентрация и профессионализм дадут сбой.

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

Работал на проекте без тестов где одно неучтенное поведение унесло 30 миллионов...

Уточните, пожалуйста, 30 миллионов чего унесло это неучтённое поведение?

Российских рублей. Унесло за несколько часов.

Появились. Стали выделять отдельно время под это. Исчезли задачи "нужно вчера".

Конечно имеет. Язык и инфраструктура вокруг него создает культуру. Абстрактные факбрики билдеров (не штука) присутствуют ТОЛЬКО в жава программах, в других языках не замечено, хотя все средства имеются.

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

С#, Python, TypeScript. Хотя в шарпе лет десять назад еще можно было встретить такую дичьЪ, но сейчас совсем нет.

Понятно что они все учли ошибки джавы, но почему в джава не учли?

Абстрактные Фабрики Билдеров это код примерно 2016 года написания, возможно до сих пор работает.

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

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

Также предлагаю изучить код Spring 5 или Hazelcast, как примеры популярных продуктов на джаве, и поискать там ваши "абстрактные билдеры". Идите предъявляйте конкретным кодерам, а не языку, который давно уже не навязывает никакого стиля, позволяя писать что угодно и как угодно.

На питоне без них никак. На жаве ваще не нужны.

Мерзотнее чтива давненько не видал. Вполне себе годная идея "будь проще" разбавлена абсолютно отвратительными тезисами, буквально вредными советами, которые могут быть поняты не так миллионом способов. По порядку:

Название переменных

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

20 строк в методе, 200 строк в классе

Авторитетный источник занимается черепомерством в прямом эфире. Привести обоснования для приведенных цифр? Не, пошел я нахер, понятно.

Полиморфизм

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

Повторяемость кода

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

5.Слабая связанность

Я пойду дальше. После прочтения этого абзаца я пошлю нахер и автора, и людей, у которых весь код - это пары из interface Service -> class ServiceImpl. Эти люди совершенно некомпетентны в вопросах связности и декаплинга.

Юнит-тесты

А здесь идет пучок демагогии про юнит тесты. Как будто первый раз, мало TDD-шников до этого было. Целый абзац воды ниочем, зато звучит злободневно.

Системность

Очередная фиксация на крудах. Все есть круд. А что не круд, то под круд должно быть подогнанно, даже если это объективно не круд.

И напоследок.

Я полностью согласен с тем, что синьорность - это в первую очередь бизнесориентированность. Я отдаю себе отчет в том, что выеживаться в проектном коде неконструктивно. И скорее всего тот графовый алгос был действительно не к месту. Можно было бы в посте рассказать об этом всратом кейсе поподробнее. Собрать мотивацию тех людей кто принимал такие решения. Расписать последствия. И идея поста была бы донесена лучше, и чтиво было бы образовательнее. Но нет, какое еще "образовательное" начало - ведь заниматься дешевым поучательством с подменой всех понятий понятий так весело!

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

 Кстати да - я душнила, юмор не понял. - неистово плюсую.

1. Название переменных

ОДИНАКОВЫЕ!!!! Не разные!!! Буква в букву! Если в базе у вас в таблице user есть поле userName, то в энтити user у вас должно быть userName, и в userDto у вас должно быть поле userName.

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

2. 20 строк в методе, 200 строк в классе

Я бы ещё убрал тупое правило каждый класс в отдельном файле.

Автор правильно пишет про преждевременную оптимизацию и маленькие классы. Если у меня простенький контроллер на 1 экшен, DTO из двух полей и простой CRUD то всё это лучше положить в одном файле User, чем городить UserController, UserDto, etc.

В идеале так вообще один класс, который имплементит все нужные интерфейсы и содержит всю реализацию в себе.

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

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

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

Ну ладно, ты достучался, меня слегка тригернуло.

Глобально - жду следующего уровня дзена, когда становится понятно, что качество кода вцелом - побочная вещь. Он работает или нет, а полиморфизмы там или что еще почти всегда без разницы: в 90% случаев в течение следующих 3-5 лет придет одна из трех задач: "разбить на микросервисы", "смерджить вот с этим сервисом", "переписать все на вот этот стек/технологию/язык", и один фиг код будут резать.
А важно мыслить от продукта - продукт работает, хорошо. И если это ужасный код с утечкой памяти с прибитым рядом скриптом, который временами перезапускает сервис... Ну чтож, это плохо, но работает. И 2 часа в месяц джуна, который добавляет в скрипт имена новых сервисов для перезапуска, дешевле, чем НадСеньор, который это дело починит. Даже если код при этом стал лучше.

Я променяю хороший код на говнокод с инструкцией. Типа, чтобы мне раз в неделю приходило письмо "у вас зависла треть серверов. Запустите скрипт Х с параметрами А и Б, который их перезапустит". И мне становится наплевать, полиморфизм там, свитч, или goto. Удовлетворяйте свою потребность к творчеству как хотите, если у меня Availability останется 99.99%. Даже если вместо красивого и изящного кода у меня будет белковый компьютер человек, который временами ручками скрипты запускает.

А в частностях сильно не согласен вот с этим

Юнит-тесты

У тестов, если они нормально написаны, есть ряд отличных функций.
Тесты - это косвенная документация. Если для вас функция больше 20 строк становится сложна для понимания, то вы должны оценить. У меня есть несколько классов, которым надо задать начальное валидное(!) состояние (какое оно?) и запустить некоторый сценарий(какие?). Где, как не в тестах, показать, какие сценарии валидны.
Тесты - это страховка он изменений коллегами. Когда я пишу некоторый класс, я неявно подразумеваю, что увеличение стороны прямоугольника увеличит его площадь в два раза. Я не хочу, чтобы какой-нибудь мудак коллега унаследовал от прямоугольника квадрат, и площадь увеличилась в 4 раза. И чтобы защитить инвариант от вмешательства - пишу тест.
Тесты - это страховка от деплоя косячного билда. Мыслим от продукта: я бы хотел деплоить что-то нерабочее как можно реже, даже в канари ветку. Мне нужны механизмы, которые минимизируют количество ошибок. Это статическая типизация, это тесты, это кодстайлы и прочее. Не пренебрегайте.
Плохо, когда тестами пытаются залечить проблему, как зеленкой оторваную ногу, но это лучше, чем мир без тестов.

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

Тесты - это страховка от деплоя косячного билда. 

Нет официальной статистики, сколько тестов происходит при билде релиза СУБД Oracle. Десятки тысяч, возможно и еще больше. Казалось бы всё отлично, и косячные билды не деплоятся, но есть нюансы.

UFO just landed and posted this here

продолжает развиваться

Судя по вбросу (если это правда, конечно), то добавление новой фичи у них занимает от года до [:|||:]. Багфиксы тоже близко 0-day и это нормально. Т.е. вроде продукт и развивается, но все относительно. В целом, как мне кажется, паттерн "не важно какой код и что он делает, главное чтобы тест был зеленым", конкретно в их ситуации, довел их до какого-то фантастического технического долга, это с учетом того, что в статье приводились еще и устаревшие версии продукта. Как там обостоят дела прямо сейчас, полагаю лучше и не знать вовсе :)

Один из врагов читаемости кода - это производительность. И это проект где производительность действительно важна.

UFO just landed and posted this here

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

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

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

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

...

Типа, чтобы мне раз в неделю приходило письмо "у вас зависла треть серверов. Запустите скрипт Х с параметрами А и Б, который их перезапустит". И мне становится наплевать, полиморфизм там, свитч, или goto. Удовлетворяйте свою потребность к творчеству как хотите, если у меня Availability останется 99.99%.

Мне кажется с таким подходом у вас availability будет далеко не 99.99%

Да почему? Масса вариантов, когда будет.
Например, зависшие сервера исключаются из балансировки с помощью circuit breaker, а в момент зависания мы просто делаем ретрай. Чутка пострадает время ответа, но не доступность сервиса.

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

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

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

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

Ну давайте пример: вам нужно по неочень тривиальной логике (тем не менее она описана бизнесом в какой-нибудь жире) отправлять сообщения пользователю. Типа если не заходил 3 месяца или только один месяц, но подписан на такие-то определение категории, или ещё что-то и то-то. Ну и какие категории порекомендовать тоже спецификация на пару страниц. Как будете релизить такой функционал?

TDD на каждый чих - перегиб.

TDD для нетривиальной задачи бизнесовой - очень даже спасает.

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

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

И то, что у бизнеса звучало как "в пятницу 13 если часовой пояс не больше N, делать то-то, а в остальных случаях другое, но не по выходным и не праздникам" превратилось в комфортный блок if-ов, которые проверены тестами и работают.

Ну метод надо подергать, чтоб понять правильно он работает или нет. В любом случае есть тесты или нет.

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

Вы упускаете суть.

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

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

Где вы возьмете API, для которого будете писать тесты?

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

Про проверки при странных случаях - соглашусь, тесты помогут. Но это не TDD и покрытие таких тестов обычно небольшое.

Почитайте про TDD.

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

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

Где почитать как сделать api, который не будет меняться при изменении требований?

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

Именно хрупкость тестов при развитии программ и напрягает при использовании tdd.

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

Другие тесты не должны падать, т.к. должны тестировать свою ответственность, а остальное мокать. Если падают - то это какие то интеграционные тесты и у них судьба такая.

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

Тупой вопрос.

Тогда что они проверяют? У тебя задача поменять метод. Ты его поменял тест сломался.

Как и узнать он сломался потому что должен был, или всё таки что-то отловил?

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

Вопрос не тупой, но очень абстрактный.

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

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

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

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

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

Ну и всё. Всё работает, но тесты некорректные. Поэтому надо полностью переписывать тесты.

Но заказ без комментария в выходной можно сделать. Парам-пам-па...

Так можно про что угодно сказать. И про код продукта, и про код тестов.

Тесты надо писать и нарабатывать опыт, как и с опытом написания кода продукта. Чуда не произойдет.

UPD: ваш комментарий звучит так, будто тесты в принципе не нужны. Я с этим не согласен.

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

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

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

Не доводы, а доводите ситуацию до абсурда.

Вы начинаете пересекать две фичи там, где у них нет пересечения.

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

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

Да дело не в больших долгоживущих проектах. Вот например я привел выше пример, который упорно товарищ автор игнорирует. Про систему рассылки. Мне в 100000 раз быстрее написать тест который просто проверяет expect(getDecision(getTestState(Day.Saturday), testUser)).to.be.eq(Decision.DoNotSendEmail). Как без юнит-теста это проверять? Поднимать ради этого тестовую почту, заводить тестовых юзеров? Алсо как удостовериться что письмо не отправилось потому чт омы его не отправили, а не потому что оно где-нибудь в спам фильтрах застряло? Классная альтернатива по сравнению с yarn test который прогонит все сценарии за пару секунд. Может быть вообще не проверять? Вопросы, вопросы...

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

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

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

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


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

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


image

Ну то есть ответ на мой вопрос выше "напишем код который кажется правильным а дальше пусть тестировщики ловят"?

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

Да, и поэтому тестировщики используют автотесты.

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

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

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

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

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

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

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

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

Так что ваше предположение, что e2e всегда сложнее и медленнее писать, чем юнит-тесты тоже ложно.

А ещё вы ответили на мой комментарий не по существу, а уйдя вообще в других обоснования.

Окей, с тестированием вам повезло, завидую.

По существу - автор топика пишет про то, что тестирование не нужно от разработчиков, тестировать должны тестировщики.

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

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

Так что ваше предположение, что e2e всегда сложнее и медленнее писать, чем юнит-тесты тоже ложно.


Я знаю ровно два случая когда юнит-тесты не проще чем e2e.

Первое, это когда продукто настолько маленький, что это одно и тоже.
Втрое, когда юнит-тестами называют e2e.


> Юнит-тесты не могут проверять бизнес-задачи по определению

Это да, но могут очень сильно это упростить. Например у меня был кейс, когда нужно взять айдишник, его отформатировать (несколько правил для разных типов айдишника), и зашифровать. У меня было с десяток юнитов типа `assert function(input) == output`. И только один тест на уровне e2e.

> времени у одного тестировщика на четырёх программистов хватает.

Там, где я работал, на этот уровень не вышли.

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

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

То есть тестировщикам тяжело проверять одно и то же по сто раз, и теперь из жалости, программист должен проверять одно и то же сто раз?

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

-----

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

То есть тестировщикам тяжело проверять одно и то же по сто раз, и теперь из жалости, программист должен проверять одно и то же сто раз?

Программистам ничего проверять не надо, проверяет компьютер сам себя.


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

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


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


Алсо


Вообще, в моём представлении, тестировщики ничего и не проверяют по сто раз, а пишут e2e тесты

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


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

Очередное прозрение очередного недо-миддла, который просветлился истиной, что прибитый гвоздями хардкод в задании на сотню-другую строк - вершина эволюции. Сразу вспомнился отличный пример здесь на хабре полностью аналогичный вышеописанному.
https://habr.com/ru/articles/153225/

А потом "внезапно" оказалось, что это - не полимиорфизм и прочие паттерны такие ужасные и сложные, а просто автор не умел ими пользоваться.
https://stdray.livejournal.com/74041.html

Автор стартапер просто, которому не приходится потом свой код поддерживать годами. В целом то рассуждения верные про простоту. Но местами явные перегибы в примитивизм с хардкодом, высокой связанностью и пр. bad-practices

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

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

А потом смотришь на количество плюсов поста и понимаешь как всё плохо в целом.

Как джуна порвало то ))) Я согласен во многом, например, для полиморфизма нужна серьёзная причина, или про связанность 1к1. Но в остальном это мрак на отдельную статью

Да! И юнит тесты это буквально ваша страховка. Вы пишете код + поставляете страховку в виде юнит тестов. Нет тестов - вы меньше уверены что оно работает и любой последующий разработчик может сломать вашу логику рефакторингом. Так что просто нужно для себя решить - вы профессионал или играетесь.

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

Я против тестов. Дикий головняк при рефакторинге и MVP. Но я за TDD, а не пишем код + тесты потом. Вот кто бы потом покрыл был все тестами, когда релизы 1.0 есть((

  • Просто наболело, юнит тесты ок конечно. Е2е устаревают через день блин. И поддерживать смысла нет вообще.

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

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

Если юнит тесты сложно писать, то скорее всего они не юнит.

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

По разному бывает...

На простые вещи юнит тесты не нужны, е2е хватит. А сложные на то и сложные что с ними все непросто.

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

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

Anonym → Logined → Phone_Confirmed

почему Logined ? правильно Logged_in

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

Просто замените строку в пуше на:

«Код: 123 456».

Просто замените все правила этой статьи на

«Не следует множить сущее без необходимости»

Ну или словами другого классика:

«Все необходимо упрощать настолько, насколько только возможно. Но не проще того»

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

А почему yagni именно "запрещает писать всякие стейт машины", на чем основываетесь (Мне искренне интересно, без всякого контекста)? Yagni про то, чтобы не писать функциональность, которая не требуется, коим образом FSM дает излишнюю функциональность?

И, как мне кажется, называть человека мудаком явно излишне, поскольку мотив, почему он сделал именно так нам неизвестен :)

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

Редко наверно тут пишу поскольку последнее время мало статей от индивидуума. А не для прокачки ЧСВ или пиара технологий.

Честно хотелось написать - как будто ты не знал как это работает на этом все. Я скажу даже больше это проблема в невосприятии критики. И боязни Сделать не по учебнику. Отсюда и появляються неиспользующиеся интерфейсы ( кто то в коментах объеснял это юнит тестами - ребят послушайте ваш код не должен подстраиваться под тесты. запомнили - закрыли тему) и прочая срань. Очень часто появляються доклады о какой то новой технологии и как мы ее круто используем в компании Х . На практике же в компании Х все мучаються с этой технологией начиная от девопсов и заканчивая разработчиками. Но зато красивый доклад на всем известной конференции звучит красиво.

А хакатоны? Я как то (будучи разрабом) и заинтересовавшись - сел и на таком хакатоне реально сделал что просили. Тоесть поднял эластик в клауде. Настроили прикрутили поиск. Решение было имплементировано. Но в результате победу отдали тем кто больше наговорил не сделав ничего.

ВЫВОД: Эффективное хорошее решение - НЕНУЖНО! Запомните. Нужно решение которое продасться куда его нужно продать. Приведу пример

  1. Алгоритм Уоршала - звучит солидно. Придет менеджер и скажет вот у нас тут все по уму и алгоримам и тд. Покупайте. А так что он продаст? Ваш безупречно работающий ИФ ?)))

  2. Всякие там супер новые и криво работающие технологии. Звучит и схема красивая - непонимающий человек смотрит - и видит ВАУ как умно разумно. А человек кооторый с этим работал смотрит и думает - все понятно как у вас работает все

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

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

Пипец.

Если тесты сложно написать - можно их просто не писать! Усложнять код, чтоб проще писать тесты - это какой-то треш.

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

Как хорошо, что с такими взглядами вы не стали писать по для атомных электростанций

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


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

Если сложно работать - то можно просто не работать!

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

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

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

Да зависит от заказчика - заказчик может быть продавец фиников на рынке - и тут я с вами согласен. Или фирма с ИТ отделом и тут решения не принимаються в таком ключе ( опустим историю с откатами). Дело в том что заказчик разный. И в большинстве своем щас люди подкованы технически или есть технические специалисты. Которые уже и принимают решение о покупке какого то продукта.

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

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

UFO just landed and posted this here

Станьте другим. Когда вы выбираете использовать ли полиморфизм, писать ли тесты на основе текущего контекста, а не потому что кто-то в интернете сказал, что есть только один путь.

Кто-то в интернете сказал, что есть ещё один путь. Теж человек сказал, попробовал - ему не понравилось.

«Ваш шестизначный пароль доступа в наше приложение: 123...»

и дальше надо клацать и куда‑то лезть и приложение закрылось, и куда оно делось так это пуш или смс был или вообще телеграм?

Просто замените строку в пуше на:

«Код: 123 456».

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

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

Ну или я не понял иронии статьи

Целых 7 лет прогаете... и что, это много? можно 7 лет перекладывать данные из базы на фронт, клепая rest-api. Ни одного пет-проекта... и что? разве это повод для гордости? наоборот, печально, что у вас нет мотивации изучать что-то новое и оттачивать навыки на пет-проектах.

Вообще, почитав ваше "резюме" https://habr.com/ru/articles/450266/, где вы в подобной же форме высмеиваете свои хардскилы, я до сих пор не пойму, то ли вы жирный тролль, то ли вы надменный глупец.

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

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

И да я постоНно в ситуации, когда код - адская ерунда, а сеньор JVM тюнит.

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

Но таких нет. Всё умеют тюнить JVM, но никто не может сделать нормальный КРУД.

Эх, после прочтения сразу внутренняя кухня мессенджера Telegram вспомнилась: где выпускники СПбГУ замутили собственный "язык программирования" для простых RPC-запросов к серверу, чтобы не использовать JSON...в итоге JSON все равно пришлось использовать, передавая его с помощью своей костыльной TL-схемы.

Хорошие советы для тех кто хочет задержаться в крудошлёпах.

В Java90 процентов проектов -крудошлепство. Если вы туда прикрутите за умную фигню, она не перестанет быть крудом, просто станет плохим крудом.!

Как и все предыдущие статьи автора, эта пронизана негативом и цинизмом типа оборотов "нах". Читать неприятно, не одобряю.
К слову, эвфемизмы запрещены правилами Хабра.
Тег "юмор" воспринимается лишь как оправдание.
Похоже, автору имеет смысл поработать над эмоциональным интеллектом.

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

Не готов это поменять на ваше одобрение)))

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

«Код: 123 456».

Что там у нас с отображением уведомлений на заблокированных телефонах?
Точно ли не будет видно кода без снятия блока в момент получения пуша или смс?

По пункту 6.

Вот например я начал делать приложение. Начал, как водится, с авторизации. Чтобы не писать всё с нуля взял встроенную авторизацию из нашей любимой "коробки". Есть таблица с столбцом user. Теперь мне нужно чтобы у usera появились ещё поля (имя адрес и тп). Я создаю ещё таблицу и связываю их с таблицей из коробки 1-1.

Если верить вам, то это неправильно. А как тогда сделать? Влезть в дебри классов из коробки, всё дополнить, перегрузить логику авторизации фреймворка?

Не знаю как вы, но я предпочитаю использовать 1-1 в этом случае...

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

Я самый умный программист, среди всех ваших работников! Я — НадНадсеньор! Сейчас докажу.

Потому что, только я могу решить задачку, которую вы уже 10 лет решить не можете

Выдайте этому человеку НадСениора, пожалуйста!

Ему скорее НадНадСениора надо

По поводу показа кода в пуше или всплывающем сообщении - тут категорически против. С точки зрения защиты кода пользователь должен сам оценить, когда ему цифры посмотреть, а не когда оно у него само показалось/всплыло на незаблокированном( или, что хуже, заблокированном) экране/мониторе. В примере молодцы, что вбили текст перед кодом достаточной длины.Пользователь может демонстрировать в это время свой экран, например в Skype. Можно смоделировать достаточно много ситуаций, когда монитор/экран виден кому-то ещё, а необходимые для подтверждения данные, вроде номера телефона, этому кому-то известны, возможно вы их даже произнесли громко и вслух некоему оператору перед этим. Это может показаться параноей, но сравнив показ кода в пуше и нет, во втором случае безопасность намного выше.

"Зачем учить географию, если есть извозчики?" (с) "Недоросль"

А если знаешь Географию, то можно из Москвы в рязань приехать через Мехико и канберра, юзая реактивный миг-31, и пешком форсировав Джомолунгму.

Я сейчас как раз чиню систему дозаправки на сверхзвуке и оформляю визу в новую Зеландию.

Будто пост на прекрасном.ит прочитал.

Настоящий сеньор может быстро разобраться в сложном и запутанном коде и написать костыль 80 уровня

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

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

Управление сложностью — самый важный технический аспект разработки ПО. По-моему, управление сложностью настолько важно, что оно должно быть Главным Техническим Императивом Разработки ПО.


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

Автор, сеньоры - это часто взрослые дядьки и не страдают от максимализма.

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

  2. И как вы уместите 30 ифов в 20 строк кода? )))) В целом да, методы и классы должны быть лаконичными, но не всегда это возможно. Да и что плохого в маленьких классах? Если у меня класс с одним методом на 10 строк кода, мне надо его слить с другим таким же классом? Как класс назвать? Зоопарк?

  3. Копипаста дала сбой :) в абстрактном классе зачем мяу? :) Не знаю как вам, но мне проще все переносить в объекты. Добавьте в ваш класс методы "Проявить радость" и "проявить агрессию". И вот, ваш Энимал перестанет быть читаемым. Зато, если открыть класс Кошка, то она будет мяукать, вилять хвостом при раздражении, мурлыкать когда ей клево. А у собаки поведение будет другим.

  4. После убирания повторяемости кода количество строчек не обязательно должно сократиться. Мы программисты, а не архиваторы. Код бывает сложно понять не из-за того, что в нем миллион строк...

  5. ... а из-за того, что он слишком связный, недостаточно декомпозированный, у единиц кода плохие имена, слишком большая вложенность и другие причины. А в хорошо структурированном коде легко разобраться, даже если проект на терабайт. Про интерфейсы с одной реализацией следует сначала задать вопрос на чем пишем. Если это TS, то наверное вы и правы. Но если это C#, то интерфейс проще замокать. А еще при написании библиотек я могу предоставить пользователям библиотеки возможность подкинуть иную реализацию (например, свой алгоритм сортировки или свой сервис даты/времени или еще что угодно другое).

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

  7. Легаси код - код не покрытый тестами? Это куда вы собеседовались, если не секрет? Да и какой смысл покрывать гуано-код тестами? На это уйдет уйма времени, потому что чтобы код легко тестировался, он должен быть хорошо написан. Если покрыть тестами код с высокой связностью, толку от этих тестов не будет. Даже не могу представить себе гуано-код полностью покрытый тестами. Я бы не смог написать эти тесты. Вот с тем, что код должен быть понятен я полностью согласен. Способность разработчика написать код и тест к нему никак не связаны. Бывает что на сложный алгоритмически код легко написать юнит тест, а бывает что на простой код написать тест сложно.

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

Одно дело, когда невозможно, а другое дело, когда

@table(name='human')

public class User{

@Column(login)

string username;}

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

Я щас с почтой работаю. Там у каждого письма с есть отправитель и получатель. Ну вроде логично, но нет.

У каждого письма есть worker и consumer.

Worker - это тот кто с письмом работает. То есть когда его запечатывают, то worker -отправитель, а когда распечвтцвают, то worker получатель.

Когда отвечают на письмо или пересылают, тут пипец... Там еще поля добавлены. Есть табличка специальная в документации, чтоб понять кто есть кто.

ну вот, я это и имел ввиду. Абстракция на верхнем уровне отличается от абстракции на нижнем

Да и какой смысл покрывать гуано-код тестами?

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

Получается рефакторинг на уровне одного метода? Такое себе...

Рефакторинг, это когда ты некоторые модули удаляешь и классы, а не внутри метода роешься

Рефакторинг кода — это процесс реструктуризации существующего кода без изменения его внешнего поведения.

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

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

Вот вроде правильные вещи говорите, но такие оторванные от жизни... Какие-то теоретические....

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

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

Вот вроде правильные вещи говорите, но такие оторванные от жизни... Какие-то теоретические....

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

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

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

Совершенно верно. Возможно я как-то не совсем ясно выразился, спасибо за более ясную формулировку.

Зачем, если ты полностью выпилишь метод вместе с классом? Что будут тестировать тесты?

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

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

порой одной из причин рефакторинга является невозможность написания юнит-тестов )

а е2е может быть слишком затратно.

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

В общем тут по ситуации

Class Animal{String type, voice(){ if (type==dog) -> gav, elseif(type==cat)->myau else->not} }
А потом из этого вырастает елочка, а потом малейшее изменение ломает к чертям все, а еще дальше, программист просто отказывается туда лезть, а в отдаленной перспективе это даже никто и понять не сможет.

Так вот первый код короче и работает быстрее.

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

Спасибо за статью! С половиной тезисов не могу полностью согласиться (думаю, у всего есть границы применимости). Но пример в начале статьи топовый!

дальше душный факт-чекинг

В статье написано, что DIY-полиморфизм через условные операторы со сравнением строк работает быстрее чем нативный динамический полиморфизм:

Так вот первый код короче и работает быстрее.

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

Вообще современные процессоры не разделают любви к if.

И да, я считаю важным обсудить это в 2к23)))

Articles