Используем method decorator в CoffeeScript(Javascript) для удобного и читаемого DRY-кода

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

    Условно проблема выглядит вот так:
    ###
    My awesome class
    ###
    class Awesome
      doFoo : (arg, cb) ->
        unless arg is 42
          return cb Error """
                          only The Answer may be an argument, but got:
                          |arg| = |#{arg}|
                          """
        cb null, "#{arg} is The Answer"
    
      doBar : (arg, cb) ->
        # hm... arg must be The Answer too
    

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


    Для избавления от копипасты мы слегка схитрим и изменим код модуля, введя одну внеклассовую функцию, и результат станет выглядеть так:
    ###
    My awesome class
    ###
    
    ensureArgIsTheAnswer = (methodBody) ->
      (arg, cb) ->
        unless arg is 42
          return cb Error """
                          only The Answer may be an argument, but got:
                          |arg| = |#{arg}|
                          """
        methodBody.call @, arg, cb
    
    class Awesome
      doFoo : ( ensureArgIsTheAnswer (arg, cb) ->
        cb null, "#{arg} is The Answer"
      )
    
      doBar : ( ensureArgIsTheAnswer (arg, cb) ->
        cb null, "#{arg*2} is The Double Answer"
      )
    


    Этот же принцип можно использовать для отладочного логирования:
    ###
    Logging method decorator
    ###
    logOnDemand = (methodBody) ->
      (args...)->
        __rval__ = methodBody.apply this, args
        if @_do_logging_
          console.log "#{args[0]} -> #{__rval__}"
        __rval__
    

    И всего, чего вам только вздумается.

    Ложка дегтя (куда уж без нее) — сами декораторы таки придется копипастить из модуля в модуль, простого и очевидного способа расшарить их я пока не нашел.
    Бочка меда (спасибо Infernal) — для использования декораторов в проекте — просто собираем их в модуле(-ях) и импортируем требуемые в нужном месте, что еще больше сокращет объем писанины и повышает читабельность.

    Этот трюк был честно куплен с книжкой CoffeeScript Ristretto, по мне — самой толковой для понимания CoffeeScript.

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

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

    Похожие публикации

    Комментарии 16

      0
      как вариант, написать класс с методом показа ошибки и использовать mixins
        0
        Мммм… Что-то как-то сходу не могу себе представить удобства от такого решения.

        Если не сильно затруднит — можно пример, причем именно с асинхронными методами, которые не результат возвращают, а вызывают коллбек?
          +1
          var logMixin  = {
          	log : function(message) {
          		console.log(message);
          	}
          };
          
          var Cat = {
          	say : function() {
          		this.log('мяу'),
          	},
          	getFood : function() {
          		$.get('/food', _.bind(this.say, this));
          	}
          };
          
          Cat = _.extend(Cat, logMixin);
          Cat.getFood();
          


          примерный код. _.extend просто объявляет все методы logMixin внутри Cat
            0
            А, ясно, спасибо.

            Ну, ИМХО декоратор тут изящнее выглядит, кроме того из них еще и адапторы можно делать, для нижележащих функций.
        0
        Ложка дегтя (куда уж без нее) — сами декораторы таки придется копипастить из модуля в модуль, простого и очевидного способа расшарить их я пока не нашел.


        Или я чего-то не понял, или вам надо сделать функцию глобальной:

        @ensureArgIsTheAnswer = ()-> ...
        
          –2
          Мне религия не позволяет пользоваться глобальными переменными :)
          Уж лучше копипаста.
            0
            Чтобы не засорять глобальное пространство, все глобальные функции можно (да и нужно) спрятать в немспейс:

            @NS ?= {}
            @NS.ensureArgIsTheAnswer = ()-> ...
            


            Если религия не позволяет даже одной глобальной переменной (собственно, неймспейса), ваш выход — AMD-модуль

              0
              Ну, неймспейс сам по себе глобальная переменная, к которым религия строга :)

              И, нет, AMD-модули это какие-то странные фиговины, которые не работают в node.js без напильника, так что наш выбор — CommonJS-only.
                +2
                Эмм… Так если у вас Node вообще не понимаю в чем проблема расшарить что-либо. Выносите декораторы в отдельный модуль и экспортируете.

                  0
                  О, и правда! :)
                  Временами случаются со мной затупы, спасибо за подсказку.
            0
            Посмотрю на досуге, но во-первых у меня объяснение вышло сильно короче и понятнее, во вторых код там кривоват.
            TextProcessor = (@processors) ->
            

            а должно быть
            class TextProcessor 
                constructor : (@processors) ->
            

            иначе
            Cannot call method 'reduce' of undefined
            
            0
            Почему бы просто не вызвать вашу вспомогательную функцию внутри нескольких методов. Окам :)
              0
              Потому что на самом деле продемонстрированный враппинг — верхушка айсберга под названием AOP, самой элегантной схемы организации кода из мне известных. Копайте глубжее :)
                0
                Копал :) Насколько я знаю аоп, о котором так много шумели в начале (типа новая парадигма и тд) скромно занял свою нишу на задворках java. и кроме усложнения кодопонимания и теоретических преимуществ впринципе он ничего не дал :), разве для оч специальных случаев. Кстати основные концепции css это аоп, а хорошего css встретишь редко.
                  0
                  Его скромное положение в основном из-за того, что он с горем пополам может быть применен в популярных языках (или делает код нечитабельным, как в perl-е), да плюс требует некоторого прогиба мозгов и лишнего кодописания (я вот хоть и понимаю прототипное наследование, но синтаксис настолько убог, что лучше CS с его псевдоклассами).
                  По мне так aop и контракты — очень классная идея, очень легко и нативно ложится на CoffeeScript/JS (есть даже пара модулей для такого классического варианта написания контрактов), нормально читается и избавляет код метода от лишнего шума.
                  ИМХО это как с коллбеками — пока их немного — все ок, но как только начинается перебор, да с развесистой логикой — проще перейти на event-ы. Хотя, каждому свое, собсно тем и хорош (для меня) JS — пластичен настолько, насколько тебе этого нужно.
                  PS. разве в приведенном примере есть сложности с кодопониманием? Ну, что-то куда-то вынеслось, но так и с миксинами такая же байда, тут уж чем больше нормализации, тем больше join-ов :)

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

            Самое читаемое