Обновить
8
0
Толмачёв Дмитрий@FiresShadow

Разработчик ПО

Отправить сообщение
Всё равно не понимаю, «стуканёт», как глагол в будущем времени, предполагает некое действие в будущем. Какое действие? Я не спорю, концепции реляционного SQL и объектной модели разные, но при чём тут принципиальная невозможность ORM когда-либо стать инструментом, для использования которого знание глубинных принципов его работы не являлось бы обязательным навыком? Различие в концепциях является лишь причиной, мешающей полностью абстрагироваться от самого существования базы данных, но не более того.

Почти любой инструмент предполагает выполнение некоторых обязательных требований. Для сборщика мусора, например, это требование не делать циклических ссылок между объектами. Но это не значит, что все инструменты — протекающие абстракции. Но современные ORM, даже при соблюдении всех подразумевающихся требований, всё равно обязывают знать как они работают: часто нужно увидеть sql-запрос, сгенерированный где-то в недрах ORM и посмотреть, какого индекса не хватает, потому что ORM не может сама создавать индексы. А если бы ORM сама заботилась об индексах и генерировала оптимальные запросы, то такой необходимости не было бы.
Не могли бы вы пожалуйста немного пояснить, что в вашем понимании означает «ORM никогда не будет настолько хороша, что никогда не стуканёт»?
вместо того, чтобы возиться с памятью, программист теперь учит, что такое управляемый код, как работает GC

Как по мне, изучить принцип работы сборщика мусора гораздо проще, чем best practices работы с памятью и методы поиска багов из-за неправильной работы с памятью. Это ж ещё нужно догадаться, что баг именно в ошибке работы с памятью, когда метод одного класса вышел за пределы массива и переписывает данные другого класса, причём с периодичностью 1 раз на 100 запусков. Так что трудозатраты при работе с памятью, имхо, всё же уменьшились.
В XIX веке некоторые примерно также объясняли невозможность полёта на Луну. Или, может быть, дело не в этом, и вы просто пророк?
Кажется, вы меня немного не поняли. Я не говорил, что гарантированно наступит светлое будущее, когда ORM ни разу не «стуканет». Я говорил, что если ORM будет настолько хороша, что никогда не «стуканет», то рядовому программисту не будет необходимости знать подробности её работы. Многие не знают как устроена файловая система, но успешно ей пользуются. Файловая система — хорошая, не протекающая абстракция.
то, что они благодаря скрытым механизмам выжили и получили ресурсы на полноценное развитие не делает их более пригодными для решения задач

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

Hibernate — родоначальник современных ORM, был написан энтузиастами в рамках опенсорсного проекта. Я не силён в истории, но уверен что с IOC-контейнером, Mock-объектами, Dynamic Proxy была примерно такая же история. Это стало возможно благодаря тому, что в языке была такая фича, как рефлексия. Это теперь для вас это библиотечная функция. Но чтобы их написание было возможно, эта фича в языке была необходима. Кроме того, рефлексия нужна и рядовым программистам. Например, сейчас я делаю удобную предобработку вызовов сервисов на общей шине, используя Dynamic Proxy, в Interceptor-е вызываю нужную функцию при помощи постороения ExpressionTree. Проще было бы вызвать через рефлексию, но тогда быстродействие просядет. Если бы в языке не было ни рефлексии, ни ExpressionTree, то пришлось бы вручную писать класс-обёртку вокруг каждого сервиса, проксируя вызов каждого метода.
Споэльски в своей статье про протекающие абстракции описал точку зрения, схожую с вашей. Однако я больше склоняюсь к высказыванию Эрика Эванса на этот счёт: «Рутинная деятельность должна быть автоматизирована. Автоматизация того, что должно быть трудом программиста, контрпродуктивно». Если инструмент автоматизирует то, в чём нужна свобода манёвра, то он ограничивает свободу действий программиста. Если абстракция инструмента протекает, то программисту приходится залезать в его внутренности. Если в инструменте есть баги, программисту приходится прикручивать костыли. Я думаю, что проблема может быть в недоработанности, недостаточной документации, плохой архитектуре инструментов, а не в их сложности. Например, современные ORM не имеют утилит для анализа запросов и автоматической генерации индексов, не умеют оптимизировать запросы, поэтому иногда приходится при помощи профилировщика смотреть, какой sql код они генерируют. Но если исправить эти и некоторые другие недоработки, то ORM станет более сложным, но более удобным инструментом, и программистам, использующим его, не будет необходимости знать sql. Таким образом, имхо, проблема не в сложности инструментов, а в их недоработках или изъянах.
А рефлексия не нужна в составе языка.

IOC-container, Mock-объекты, Dynamic Proxy, ORM в своей реализации используют рефлексию. И если уметь ими пользоваться, то они избавляют от рутинной работы. Также, рефлексия может быть полезна при написании своих мини-фреймворков. Например, передать лямбда-выражением переменную, а рефлексией получить её имя для логирования и рефлексией установить значение этой переменной. Конечно, рутинную работу можно проделать и самому, но быстрее и проще воспользоваться инструментом.

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

Это наивное детское представление о мире. Зарплата не зависит ни от пользы работы, ни от её сложности. Она зависит от того, за какую зарплату другие люди согласны делать ту же самую работу. Даже если все программисты сговорятся и не будут использовать сложные инструменты, но при этом автоматизировать процессы всё равно будет выгоднее, чем делать вручную, то зарплаты не изменятся. Кроме того, заказчику безразлично, с использованием каких инструментов написана программа. Ему важна лишь скорость написания, стоимость сопровождения, надёжность, цена и актуальность тех задач, которые решает программа. И эти факторы (помимо актуальности задач) успешнее достигаются, как ни странно, квалифицированными программистами, владеющими нужными инструментами. Конечно, можно рутинную работу и вручную делать, но скорость написания упадёт и стоимость сопровождения увеличится. Поэтому нанять одного квалифицированного программиста выгоднее, чем парочку неквалифицированных (даже если они согласны работать только за большую зарплату). Кроме того, квалифицированные спецы зачастую способны делать то, что неквалифицированные не способны сделать в принципе. Поэтому у них и зарплата выше, и дело вовсе не в мировом заговоре программистов, специально делающих сложные инструменты.
Интересно, а паттерн MVC по-вашему имеет отношение к разработке архитектуры?
Но самое-то грустное не в этом. Самое грустное в том, что из вашей схемы совершенно не понятно, что именно в реальности надо делать. Она бессмысленна


Пожалуй, комментарии в духе «статья плохая, потому что после прочтения у меня остались вопросы или я что-то не понял» являются очень распространенными на хабре. Если есть вопросы, то можно загуглить, скачать книгу, спросить у автора. Но зачем критиковать? Чтобы полностью осветить все вопросы по проектированию, нужна не одна книга. А это просто статья. И она вполне могла бы быть первой главой в какой-нибудь книге. Она задаёт общее направление для рассуждений, упорядочивает мысли и заинтересовывает читателя в процессе проектирования. Разве этого мало?
Побоку — потому что сбоку нарисовано?
Сразу вспомнился вот этот топик на тостере от того же автора
По-моему, по такой схеме вполне можно получить выигрыш в производительности. Не проверял это на практике, но исхожу из логических рассуждений.


Скорее всего, ощутимого выигрыша в производительности не будет. Основная нагрузка на сборщик мусора ложится после вызова context.Dispose(), который освобождает кэшированные результаты выполнения запросов и некоторые другие ресурсы. При этом нагрузка на сборщик мусора при сборе непосредственно самих ссылок на context не такая уж и существенная. Когда речь заходит об оптимизации, нужно быть осторожным: экономия на спичках не даёт заметного выигрыша в производительности (пользователь даже не заметит — была проведена оптимизация или нет), но такие оптимизации могут сделать код более запутанным и неочевидным.
Кроме того, напомню, зачем вообще нужен сборщик мусора: он освобождает программиста от заботы о том, в какой момент времени нужно освободить выделенную память. Если требования к быстродействию достаточно высоки и есть необходимость отслеживать время жизни объектов в каждой функции, то возможно следует воспользоваться языком программирования без сборщика мусора. Хотя, скорее всего, дешевле и проще было бы поставить более мощный сервер или, в данном случае, рассмотреть вариант использования какой-нибудь легковесной ORM без внутреннего кэша.
Ещё примеры алгоритма борьбы с зацикливаниями.

p() :- consult(«name.txt», имяБД),
retract(fact(N,A,B)),
asserta(fact(random(9),A,B)),
fail;
write(«end»).

Кладём в контейнер пометку об обходе consult(«name.txt», имяБД). consult добавляет в программу новые предикаты; для каждого добавленного предиката извлекаем из контейнера пометки о тех предикатах, поведение которых изменилось после добавления нового предиката. consult не «заходит» ни в один пользовательский предикат. Извлекаем consult из контейнера. Потом обходится предикат retract(fact(N,A,B)), кладём в контейнер пометку об обходе retract(fact(N,A,B)). Извлекаем из контейнера пометки обо всех предикатах, поведение которых изменилось после удаления предиката fact(N,A,B). Эти предикаты можно найти рекурсивно: это непосредственно fact, все предикаты в которых использовался fact (например, если rule():-someRule1(), fact(X,Y,Z), someRule2(), то поведение rule изменилось), все предикаты в которых использовались предикаты в которых использовался fact, и так далее. Достаём из контейнера информацию об обходе retract(fact(N,A,B)). Кладём в контейнер информацию об обходе asserta(fact(random(9),A,B)). Извлекаем из контейнера все предикаты, поведение которых изменилось при добавлении предиката fact. Извлекаем из контейнера информацию об обходе asserta(fact(random(9),A,B)).

p() :- if 42 <> random(10000) then p() end if.

Кладём в контейнер информацию об обходе p(). Кладём в контейнер информацию об обходе random(10000). При вызове random, введении данных извне, добавлении новых предикатов или их удалении, нужно извлечь из контейнера все предикаты, поведение которых может отличаться при следующем их обходе. То, как найти такие предикаты было описано ранее, в данном случае это p(), извлекаем пометку о p() из контейнера. Допустим random выдал 5. Извлекаем из контейнера пометку об обходе random(10000). Теперь мы снова пытаемся обойти p(). В контейнере нет пометки об обходе p(), поэтому мы не детектируем зацикливание и разрешаем это сделать. Помещаем в контейнер пометку об обходе p(). Помещаем в контейнер пометку об обходе random(10000). Допустим random выдал 42. Извлекаем из контейнера пометку об обходе random(10000). Извлекаем из контейнера пометку об обходе p(). Снова пытаемся извлечь пометку об обходе p(), но её нет в контейнере — ничего страшного, нет ну и ладно, продолжаем выполнение программы.

p() :- if -42 <> random(10000) then p() end if.

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

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

Давайте представим себе воображаемую ситуацию: программист и Капитан Очевидность собрались вместе, чтобы устранить этот недочёт в алгоритме.

П: Алгоритм считает, что если программа повторно зашла в предикат с теми же самыми параметрами, то программа зациклилась. Когда добавляются новые правила, алгоритм продолжает считать также, но это неверно.
КО: А пусть он так не считает!
П: Хорошо, после того как добавились новые предикаты, алгоритм не будет считать что захождение в тот же самый предикат привело к зацикливанию. Но как добиться того, чтобы алгоритм перестал так считать?
КО: Для того, чтобы ответить на этот вопрос, нужно знать подробности вашего алгоритма.
П: Ну, если вкратце, мы кладём в контейнер запись с пометкой что мы обходили предикат. Потом на основании наличия пометки решаем, произошло ли зацикливание. Есть пометка — считаем что есть зацикливание, нет пометки — нет зацикливания.
КО: Ну так сделайте так, чтобы не было пометки!
П: Хорошо, при добавлении нового предиката удаляем пометки из контейнера. Но какие пометки нужно убрать? Убирать все пометки нецелесообразно. Добавление нового предиката может оказывать влияние не на все другие предикаты, а лишь на некоторые из них.
КО: Ну так вы и убирайте только те, на которые добавление нового предиката могло оказать влияние!
П: Спасибо, Капитан!

(в диалоге описано как устранить недочёт в алгоритме)
Пояснение по борьбе с исключениями путём модификации компилятора

fact(2).
fact(4).
p(N) :- fact(I), I>N, p(N).
p(N) :- stdio::write(N).

Проще будет понять объяснение, если одновременно читать и трассировать программу. Допустим в goal задали Prolog-у вопрос, истинно ли p(10). В таком случае не будет зацикливания. Prolog заходит в строку 3, в контейнер (например, в красно-черное дерево) кладём объект, состоящий из информации об обходе предиката — p(10), а также хэша этой информации. дальше видим fact(I). кладём в контейнер fact(output). видим факт fact(2). возвращаемся из fact(I) и одновременно достаём из контейнера fact(output). Далее I>N, которое не выполняется (2 > 10 ложно). Далее пролог возвращается в fact(I) и начинает перебирать остальные возможные I. Снова кладем в контейнер fact(output). Видим fact(4). Возвращаемся из fact(I) и одновременно достаём из контейнера fact(output). Видим что I>N — ложно. Далее p(N) :- stdio::write(N), пролог выводит 10. Достаём из контейнера p(10). Программа завершается.

Теперь давайте разберём случай, когда зацикливание происходит. В goal задали вопрос истинно ли p(1). Поначалу выполнение программы похоже. Кладём в контейнер p(1), кладём fact(output), достаём fact(output) и теперь I=2. Теперь снова пытаемся положить в контейнер p(1), но видим что в контейнере есть объект с таким же хэшем, сравниваем сами объекты и видим что они равны. Обнаружили зацикливание. Кидаем исключение. Перехватываем исключение там где происходит ближайший перебор значений, то есть при переборе fact(I). Продолжаем перебор. Теперь I=4, снова зацикливание, снова кидаем исключение и перехватываем на переборе fact(I). Prolog видит что возможные значения I закончились, и продолжает программу, после чего выводит 1 и корректно завершается.

Полное обсуждение тут
А какие преимущества у Stepic.org по сравнению с lektorium.tv?
забыл добавить, что в моих шуточных формулах всё измеряется в коэффициентах от 0 до 1.
Первая, это насколько то, что делает программист, эстетически правильно: насколько его код красив, воспринимаем другими. И второе, что может как раз конфликтовать с первым, но при этом очень важно, это то, насколько он умеет своими действиями, своим кодом достигать результата по каким-то метрикам. Например, качества поиска.

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

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

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

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

А если автор никуда не уйдёт, и передачи кода другому человеку не будет, то, выходит, можно и не стараться? (irony)

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

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

Извините, если кого обидел.

Информация

В рейтинге
Не участвует
Откуда
Россия
Дата рождения
Зарегистрирован
Активность