HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#статьи</a></p>
1 <p><a>#статьи</a></p>
2 <ul><li>22 апр 2021</li>
2 <ul><li>22 апр 2021</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Нет, мы не опечатались - оно существует. А умеет такое, с чем совладают немногие разрабы: рассказываем, что - и как не хватить с этим лиха.</p>
4 </ul><p>Нет, мы не опечатались - оно существует. А умеет такое, с чем совладают немногие разрабы: рассказываем, что - и как не хватить с этим лиха.</p>
5 <p>скриншот из игры necromunda: hired gun / streum on studio</p>
5 <p>скриншот из игры necromunda: hired gun / streum on studio</p>
6 <p>Переводчик, специалист по авиационной безопасности, начинающий веб-разработчик. Убеждён, что любой человек может сочетать абсолютно разные интересы и сферы профессиональной деятельности, ведь наши возможности безграничны.</p>
6 <p>Переводчик, специалист по авиационной безопасности, начинающий веб-разработчик. Убеждён, что любой человек может сочетать абсолютно разные интересы и сферы профессиональной деятельности, ведь наши возможности безграничны.</p>
7 <p><strong><strong>об авторе</strong></strong></p>
7 <p><strong><strong>об авторе</strong></strong></p>
8 <p>Топовый автор Medium. Пишет о технологиях, об образе жизни, личном опыте и многом другом.</p>
8 <p>Топовый автор Medium. Пишет о технологиях, об образе жизни, личном опыте и многом другом.</p>
9 <p>Кто не знает про объектно-ориентированное программирование, да и функциональное явно на слуху, а вот об аспектно-ориентированном вы когда-нибудь слышали?</p>
9 <p>Кто не знает про объектно-ориентированное программирование, да и функциональное явно на слуху, а вот об аспектно-ориентированном вы когда-нибудь слышали?</p>
10 <p>Пусть это и звучит как фраза неисправимого гика, но аспектно-ориентированное программирование (АОП) - источник великой силы. Даже удивительно, почему мы до сих пор прибегаем к нему так редко.</p>
10 <p>Пусть это и звучит как фраза неисправимого гика, но аспектно-ориентированное программирование (АОП) - источник великой силы. Даже удивительно, почему мы до сих пор прибегаем к нему так редко.</p>
11 <p>Что самое крутое, АОП легко сочетается как с функциональным, так и с объектно-ориентированным программированием (ООП).</p>
11 <p>Что самое крутое, АОП легко сочетается как с функциональным, так и с объектно-ориентированным программированием (ООП).</p>
12 <p>Что ж, разберёмся, что такое АОП и чем оно полезно JavaScript-разработчикам.</p>
12 <p>Что ж, разберёмся, что такое АОП и чем оно полезно JavaScript-разработчикам.</p>
13 <p>Аспектно-ориентированное программирование позволяет внедрять новый код в готовые функции или объекты, не затрагивая их целевой логики (а следовательно, не влияя на поведение программ).</p>
13 <p>Аспектно-ориентированное программирование позволяет внедрять новый код в готовые функции или объекты, не затрагивая их целевой логики (а следовательно, не влияя на поведение программ).</p>
14 <p>Предполагается, хотя и не обязательно, что такой код решает некие общие задачи (добавляет так называемую сквозную функциональность). С его помощью можно, скажем, вести журналирование или отладку.</p>
14 <p>Предполагается, хотя и не обязательно, что такой код решает некие общие задачи (добавляет так называемую сквозную функциональность). С его помощью можно, скажем, вести журналирование или отладку.</p>
15 <p><strong>Разберём на примере</strong></p>
15 <p><strong>Разберём на примере</strong></p>
16 <p>Допустим, вы реализовали бизнес-логику и вдруг поняли, что забыли про логирующий код. Тогда, по обыкновению, вы выносите логику журналирования в отдельный модуль, а затем проходитесь по функциям, добавляя информацию для ведения журнала.</p>
16 <p>Допустим, вы реализовали бизнес-логику и вдруг поняли, что забыли про логирующий код. Тогда, по обыкновению, вы выносите логику журналирования в отдельный модуль, а затем проходитесь по функциям, добавляя информацию для ведения журнала.</p>
17 <p>А теперь представьте, что с помощью одной-единственной строки кода логгер можно внедрить в любой метод. А срабатывать он будет в некоторые особые моменты, связанные с выполнением этого метода. Здорово?</p>
17 <p>А теперь представьте, что с помощью одной-единственной строки кода логгер можно внедрить в любой метод. А срабатывать он будет в некоторые особые моменты, связанные с выполнением этого метода. Здорово?</p>
18 <p>Познакомимся с тремя ключевыми понятиями АОП. Они прояснят суть парадигмы и помогут изучать её дальше.</p>
18 <p>Познакомимся с тремя ключевыми понятиями АОП. Они прояснят суть парадигмы и помогут изучать её дальше.</p>
19 <ul><li><strong>Аспекты (aspects, "что")</strong> - это добавочное поведение, которое нужно включить в целевой код. В контексте JavaScript это функции, которые инкапсулируют нужное нам поведение.</li>
19 <ul><li><strong>Аспекты (aspects, "что")</strong> - это добавочное поведение, которое нужно включить в целевой код. В контексте JavaScript это функции, которые инкапсулируют нужное нам поведение.</li>
20 <li><strong>Срезы (pointcuts, "где")</strong>указывают место в целевом коде, куда следует внедрить<strong>аспект</strong>. Теоретически оно может быть любым, но на практике такое выходит не всегда. И всё же вы можете указывать условия вроде "все методы моего объекта", или "только этот конкретный метод", или даже что-то типа "все методы, которые начинаются с get_".</li>
20 <li><strong>Срезы (pointcuts, "где")</strong>указывают место в целевом коде, куда следует внедрить<strong>аспект</strong>. Теоретически оно может быть любым, но на практике такое выходит не всегда. И всё же вы можете указывать условия вроде "все методы моего объекта", или "только этот конкретный метод", или даже что-то типа "все методы, которые начинаются с get_".</li>
21 <li><strong>Советы (advice, "когда")</strong>определяют момент, когда должен выполняться код<strong>аспекта</strong>, например:</li>
21 <li><strong>Советы (advice, "когда")</strong>определяют момент, когда должен выполняться код<strong>аспекта</strong>, например:</li>
22 </ul><ul><li>before (перед вызовом метода),</li>
22 </ul><ul><li>before (перед вызовом метода),</li>
23 <li>after (после возврата в точку вызова),</li>
23 <li>after (после возврата в точку вызова),</li>
24 <li>around (до вызова метода и при возврате из него),</li>
24 <li>around (до вызова метода и при возврате из него),</li>
25 <li>whenThrowing (когда метод вызывает исключение) и так далее.</li>
25 <li>whenThrowing (когда метод вызывает исключение) и так далее.</li>
26 </ul><p>Если совет указывает на время, когда код уже выполнен, аспект перехватит возвращаемое значение и сможет его перезаписать.</p>
26 </ul><p>Если совет указывает на время, когда код уже выполнен, аспект перехватит возвращаемое значение и сможет его перезаписать.</p>
27 <p>Очевидно, что с АОП довольно легко создать библиотеку для добавления журналирования в существующую бизнес-логику, даже если сама она реализована, например, на ООП. Достаточно заменить соответствующие методы целевого объекта на пользовательскую функцию, которая в нужное время добавит логику аспекта, а затем вызовет исходный метод.</p>
27 <p>Очевидно, что с АОП довольно легко создать библиотеку для добавления журналирования в существующую бизнес-логику, даже если сама она реализована, например, на ООП. Достаточно заменить соответствующие методы целевого объекта на пользовательскую функцию, которая в нужное время добавит логику аспекта, а затем вызовет исходный метод.</p>
28 <p>Закрепим теорию практикой.</p>
28 <p>Закрепим теорию практикой.</p>
29 <p>Надеюсь, вам, как и мне, легче учиться на наглядных примерах. Поэтому вот вам реализация метода inject, который добавляет поведение на основе АОП. Убедитесь, как это просто и удобно.</p>
29 <p>Надеюсь, вам, как и мне, легче учиться на наглядных примерах. Поэтому вот вам реализация метода inject, который добавляет поведение на основе АОП. Убедитесь, как это просто и удобно.</p>
30 // Вспомогательная функция для вызова всех методов объекта const getMethods = (obj) =&gt; Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(item =&gt; typeof obj[item] === 'function') // Заменяем оригинальный метод нашей функцией, которая вызовет наш аспект, когда укажет совет function replaceMethod(target, methodName, aspect, advice) { const originalCode = target[methodName] target[methodName] = (...args) =&gt; { if(["before", "around"].includes(advice)) { aspect.apply(target, args) } const returnedValue = originalCode.apply(target, args) if(["after", "around"].includes(advice)) { aspect.apply(target, args) } if("afterReturning" == advice) { return aspect.apply(target, [returnedValue]) } else { return returnedValue } } } module.exports = { // Экспорт главного метода: внедряем аспект где и когда нужно inject: function(target, aspect, advice, pointcut, method = null) { if(pointcut == "method") { if(method != null) { replaceMethod(target, method, aspect, advice) } else { throw new Error("Trying to add an aspect to a method, but no method specified") } } if(pointcut == "methods") { const methods = getMethods(target) methods.forEach( m =&gt; { replaceMethod(target, m, aspect, advice) }) } } }<p>Как видите, я вас не обманул. Хотя код выше охватывает не все варианты применения, он поможет разобраться в следующем примере.</p>
30 // Вспомогательная функция для вызова всех методов объекта const getMethods = (obj) =&gt; Object.getOwnPropertyNames(Object.getPrototypeOf(obj)).filter(item =&gt; typeof obj[item] === 'function') // Заменяем оригинальный метод нашей функцией, которая вызовет наш аспект, когда укажет совет function replaceMethod(target, methodName, aspect, advice) { const originalCode = target[methodName] target[methodName] = (...args) =&gt; { if(["before", "around"].includes(advice)) { aspect.apply(target, args) } const returnedValue = originalCode.apply(target, args) if(["after", "around"].includes(advice)) { aspect.apply(target, args) } if("afterReturning" == advice) { return aspect.apply(target, [returnedValue]) } else { return returnedValue } } } module.exports = { // Экспорт главного метода: внедряем аспект где и когда нужно inject: function(target, aspect, advice, pointcut, method = null) { if(pointcut == "method") { if(method != null) { replaceMethod(target, method, aspect, advice) } else { throw new Error("Trying to add an aspect to a method, but no method specified") } } if(pointcut == "methods") { const methods = getMethods(target) methods.forEach( m =&gt; { replaceMethod(target, m, aspect, advice) }) } } }<p>Как видите, я вас не обманул. Хотя код выше охватывает не все варианты применения, он поможет разобраться в следующем примере.</p>
31 <p>А сперва обратите внимание на реализацию replaceMethod. Вот где творится вся магия. Именно здесь создаётся новая функция, именно тут мы решаем, когда вызывать аспект и что делать со значением, которое он возвращает.</p>
31 <p>А сперва обратите внимание на реализацию replaceMethod. Вот где творится вся магия. Именно здесь создаётся новая функция, именно тут мы решаем, когда вызывать аспект и что делать со значением, которое он возвращает.</p>
32 <p>Теперь покажем, как использовать нашу новую библиотеку:</p>
32 <p>Теперь покажем, как использовать нашу новую библиотеку:</p>
33 const AOP = require("./aop.js") class MyBussinessLogic { add(a, b) { console.log("Calling add") return a + b } concat(a, b) { console.log("Calling concat") return a + b } power(a, b) { console.log("Calling power") return a ** b } } const o = new MyBussinessLogic() function loggingAspect(...args) { console.log("== Calling the logger function ==") console.log("Arguments received: " + args) } function printTypeOfReturnedValueAspect(value) { console.log("Returned type: " + typeof value) } AOP.inject(o, loggingAspect, "before", "methods") AOP.inject(o, printTypeOfReturnedValueAspect, "afterReturning", "methods") o.add(2,2) o.concat("hello", "goodbye") o.power(2, 3)<p>Ничего сверхсложного: один базовый объект с тремя методами. Мы внедрили пару общих для всех них аспектов: один выводит в лог полученные атрибуты, а другой анализирует возвращаемое значение и выводит его тип.</p>
33 const AOP = require("./aop.js") class MyBussinessLogic { add(a, b) { console.log("Calling add") return a + b } concat(a, b) { console.log("Calling concat") return a + b } power(a, b) { console.log("Calling power") return a ** b } } const o = new MyBussinessLogic() function loggingAspect(...args) { console.log("== Calling the logger function ==") console.log("Arguments received: " + args) } function printTypeOfReturnedValueAspect(value) { console.log("Returned type: " + typeof value) } AOP.inject(o, loggingAspect, "before", "methods") AOP.inject(o, printTypeOfReturnedValueAspect, "afterReturning", "methods") o.add(2,2) o.concat("hello", "goodbye") o.power(2, 3)<p>Ничего сверхсложного: один базовый объект с тремя методами. Мы внедрили пару общих для всех них аспектов: один выводит в лог полученные атрибуты, а другой анализирует возвращаемое значение и выводит его тип.</p>
34 <p><strong>Два аспекта - две строки кода (вместо шести, которые понадобились бы без АОП).</strong></p>
34 <p><strong>Два аспекта - две строки кода (вместо шести, которые понадобились бы без АОП).</strong></p>
35 <p>Вот результат, который мы увидим в консоли:</p>
35 <p>Вот результат, который мы увидим в консоли:</p>
36 <p>Разобравшись, что такое АОП и на что оно способно, вы наверняка догадываетесь, почему оно вызывает такой интерес.</p>
36 <p>Разобравшись, что такое АОП и на что оно способно, вы наверняка догадываетесь, почему оно вызывает такой интерес.</p>
37 <p>Пробежимся по его главным преимуществам.</p>
37 <p>Пробежимся по его главным преимуществам.</p>
38 <p>Я вообще большой поклонник инкапсуляции - благодаря ей код легче читать и поддерживать, а также использовать повторно во всём проекте.</p>
38 <p>Я вообще большой поклонник инкапсуляции - благодаря ей код легче читать и поддерживать, а также использовать повторно во всём проекте.</p>
39 <p>Логика, которую вы реализуете в советах и срезах, повышает гибкость при внедрении<strong>ваших аспектов</strong>. А это, в свою очередь, позволяет вам включать и отключать различные<strong>аспекты вашей логики</strong>(сорри за каламбур) прямо при выполнении программы.</p>
39 <p>Логика, которую вы реализуете в советах и срезах, повышает гибкость при внедрении<strong>ваших аспектов</strong>. А это, в свою очередь, позволяет вам включать и отключать различные<strong>аспекты вашей логики</strong>(сорри за каламбур) прямо при выполнении программы.</p>
40 <p>Аспекты - это, по сути, компоненты, небольшие независимые фрагменты кода, которые могут работать где угодно. Если они правильно написаны, то их легко применять в других проектах.</p>
40 <p>Аспекты - это, по сути, компоненты, небольшие независимые фрагменты кода, которые могут работать где угодно. Если они правильно написаны, то их легко применять в других проектах.</p>
41 <p>Ничто не идеально, и АОП тоже есть за что поругать.</p>
41 <p>Ничто не идеально, и АОП тоже есть за что поругать.</p>
42 <p>В силе этого подхода критики видят и его уязвимость: он скрывает логику и сложность, его применение приводит к побочным эффектам, причину которых трудно понять сходу.</p>
42 <p>В силе этого подхода критики видят и его уязвимость: он скрывает логику и сложность, его применение приводит к побочным эффектам, причину которых трудно понять сходу.</p>
43 <p>И отчасти противники АОП правы. Эта парадигма позволяет добавить в существующие объекты нехарактерное им поведение или даже заменить всю их логику. Конечно, АОП появилось совсем не для этого, и в примере выше я показывал вовсе не такие "возможности".</p>
43 <p>И отчасти противники АОП правы. Эта парадигма позволяет добавить в существующие объекты нехарактерное им поведение или даже заменить всю их логику. Конечно, АОП появилось совсем не для этого, и в примере выше я показывал вовсе не такие "возможности".</p>
44 <p>Однако АОП позволяет сделать всё, что вы хотите. А такая вседозволенность, помноженная на неопытность разработчика, открывает ящик Пандоры.</p>
44 <p>Однако АОП позволяет сделать всё, что вы хотите. А такая вседозволенность, помноженная на неопытность разработчика, открывает ящик Пандоры.</p>
45 <p>Банально это или нет, повторю легендарную фразу из комиксов:</p>
45 <p>Банально это или нет, повторю легендарную фразу из комиксов:</p>
46 <p>С великой силой приходит и большая ответственность.</p>
46 <p>С великой силой приходит и большая ответственность.</p>
47 <p>Одним словом, АОП - обязывает. Обязывает программиста соответствовать. Это не игрушка для рядового кодера, а выбор продвинутого спеца, который освоил лучшие практики разработки и может правильно сочетать АОП с ними.</p>
47 <p>Одним словом, АОП - обязывает. Обязывает программиста соответствовать. Это не игрушка для рядового кодера, а выбор продвинутого спеца, который освоил лучшие практики разработки и может правильно сочетать АОП с ними.</p>
48 <p>Да, в неумелых руках этот инструмент способен навредить, но это едва ли повод от него отказываться. Потому что с АОП можно сделать и много хорошего: например, собрать части общей логики в едином фрагменте и внедрить его куда угодно - одной строкой кода. Как по мне, этот мощный инструмент стоит изучать и применять.</p>
48 <p>Да, в неумелых руках этот инструмент способен навредить, но это едва ли повод от него отказываться. Потому что с АОП можно сделать и много хорошего: например, собрать части общей логики в едином фрагменте и внедрить его куда угодно - одной строкой кода. Как по мне, этот мощный инструмент стоит изучать и применять.</p>
49 <p>Аспектно-ориентированное программирование отлично дополняет ООП. Благодаря динамической природе JavaScript применять АОП с ним очень легко, как я и показал на примерах.</p>
49 <p>Аспектно-ориентированное программирование отлично дополняет ООП. Благодаря динамической природе JavaScript применять АОП с ним очень легко, как я и показал на примерах.</p>
50 <p>АОП позволяет выделять код в модули, разделять логику и переиспользовать разработки в других проектах.</p>
50 <p>АОП позволяет выделять код в модули, разделять логику и переиспользовать разработки в других проектах.</p>
51 <p>Конечно, если применять АОП неумело, то можно всё запутать, но при правильном подходе оно позволяет создавать более чистый и понятный код.</p>
51 <p>Конечно, если применять АОП неумело, то можно всё запутать, но при правильном подходе оно позволяет создавать более чистый и понятный код.</p>
52 <a>Научитесь: Профессия Фронтенд-разработчик + ИИ Узнать больше</a>
52 <a>Научитесь: Профессия Фронтенд-разработчик + ИИ Узнать больше</a>