1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. We have recently started the 16th edition of the Mini Mapping Contest. The theme is mini RPG. Do check it out and have fun.
    Dismiss Notice
  4. Dismiss Notice
  5. The Highway to Hell has been laid open. Come along and participate in the 5th Special Effect Contest.
    Dismiss Notice
  6. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

"DRY" programming in JASS?

Discussion in 'The Lab' started by Wadjet, Jul 5, 2017.

  1. Wadjet

    Wadjet

    Joined:
    Sep 14, 2009
    Messages:
    260
    Resources:
    6
    Skins:
    1
    Template:
    5
    Resources:
    6
    So I have been taking some programming lessons recently and learned of the concept "DRY" (do not repeat yourself). Which basically means avoid using the same function over and over again.

    Example:

    DRY
    Code (vJASS):

    function Trigger_Actions_GetRandomInt takes integer lowBound, integer highBound returns integer
        return GetRandomInt(lowBound, highBound)
    endfunction
    function Trigger_Actions takes nothing returns nothing
        local integer intOne = Trigger_Actions_GetRandomInt(0, 5)
        local integer intTwo = Trigger_Actions_GetRandomInt(0, 5)
        local integer intThree = Trigger_Actions_GetRandomInt(0, 5)
    endfunction
     


    Not DRY
    Code (vJASS):

    function Trigger_Actions takes nothing returns nothing
        local integer intOne = GetRandomInt(0, 5)
        local integer intTwo = GetRandomInt(0, 5)
        local integer intThree = GetRandomInt(0, 5)
    endfunction
     


    Does DRY not apply in JASS? Since I've seen many JASS coders don't use the concept of DRY. And which alternative is better?
     
  2. Jampion

    Jampion

    JASS Reviewer

    Joined:
    Mar 25, 2016
    Messages:
    1,287
    Resources:
    0
    Resources:
    0
    In this case it makes no sense to use Trigger_Actions_GetRandomInt instead of GetRandomInt, because both do exactly the same. Trigger_Actions_GetRandomInt is one additional function call so it's slower. That's why many users mostly use natives. If you write your own function it should offer a lot more to be worth using it.
    If you would for example make your GetRandomInt to have a higher probability to take numbers close to the average of lowBound and highBound, then it would be complicated enough, so it would make sense to use a custom function.
    I mean in your case it makes no difference if you repeat Trigger_Actions_GetRandomInt(0, 5) or GetRandomInt(0, 5), but if Trigger_Actions_GetRandomInt(0, 5) would be 10 lines long, it would make a lot of sense to use Trigger_Actions_GetRandomInt(0, 5) instead of these 10 lines everytime.
     
  3. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,693
    Resources:
    18
    Maps:
    1
    Spells:
    11
    Tutorials:
    6
    Resources:
    18
    For it to be worth it you need at least two lines in the function you call over and over.
    You basically just made a bad BJ function, which is banned in jass for good reason.
     
  4. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,221
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    This is more a topic of redundance than dry.

    dry is more:

    function a{
    -- do_some_a()
    -- do_some_b()
    -- do_some_c()
    }

    function b{
    -- do_some_a()
    -- do_some_b()
    -- do_some_c()
    -- do_some_d()
    }

    .... ^we want to call function "b", there is nothing redundant, but we come in conflict with dry -- we should do:

    function b{
    --call a()
    -- do_some_d()
    }

    as we repeat the same logics, so we in some cases can centralize it and only do the logics in function a. But this also makes only sense to some extend.

    Edit:

    You should not repeat yourself when it makes sense to connect content logicaly.
    But on the other side you want autonomous code pieces, which don't need logics of a "god class" or such thing, for every single line.
    Somewhere between spaghetti and ravioli code is the good. : )
     
    Last edited: Jul 5, 2017
  5. aple

    aple

    Joined:
    May 20, 2009
    Messages:
    811
    Resources:
    2
    Maps:
    1
    Tutorials:
    1
    Resources:
    2
    Coming from someone who isn't really a programmer in the slightest, even to me this concept sounds like a pretty bad practice. You shouldn't force yourself to make sure you don't repeat because you'll just end up writing very inefficient code in an attempt to make sure nothing repeats. I guess in a case like JASS (Or really any other scripting language, I guess) it doesn't totally matter because you're not writing literally hundreds of thousands, to potentially even millions, of lines of code to build a fresh program from the ground up, but in that hypothetical case where you are doing that I think it would be a pretty bad idea to try and stick to that philosophy to the T.

    Maybe if you're trying to learn how everything works, this concept can be useful.
     
  6. Kaijyuu

    Kaijyuu

    Joined:
    Jun 2, 2004
    Messages:
    848
    Resources:
    0
    Resources:
    0
    What I always learned was "do not copy/paste code". Any time you find yourself repeating a series of actions (or a similar series of actions), try to instead make a function call that does it in 1 line. This makes code maintenance easier, since any updates will only have to be done once instead of everywhere. Something newbies sometimes fall into would be failing to use loops and/or recursion, which is sort of the same idea too. Redundant code is bad. It makes maintenance a headache.
     
  7. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,804
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    I do not think the example is correct. I would imagine they should be...
    DRY
    Code (vJASS):

    function Trigger_Actions_GetRandomInt takes nothing returns integer
        return GetRandomInt(0, 5)
    endfunction
    function Trigger_Actions takes nothing returns nothing
        local integer intOne = Trigger_Actions_GetRandomInt()
        local integer intTwo = Trigger_Actions_GetRandomInt()
        local integer intThree = Trigger_Actions_GetRandomInt()
    endfunction
     

    Not DRY
    Code (vJASS):

    function Trigger_Actions takes nothing returns nothing
        local integer intOne = GetRandomInt(0, 5)
        local integer intTwo = GetRandomInt(0, 5)
        local integer intThree = GetRandomInt(0, 5)
    endfunction
     


    In the DRY example the procedural coupling from the function arguments is removed. Why this is good is that the range of the random numbers can now be maintained in just 1 place, instead of 3 and potentially even more.

    I personally try to do this whenever possible. However be aware that it is slightly detrimental in JASS due to the lack of optimizing compilers. Where as in C++ and Java there is a good chance the function call will be automatically in-lined for no overhead, in JASS it remains a function call which the JASS virtual machine performs. This is not a concern for most code but performance intensive code, eg a projectile system, should rather use the less maintainable but more efficient non DRY form for optimization. JASS is one of those languages where one has to trade maintainability to optimize.
     
  8. Wadjet

    Wadjet

    Joined:
    Sep 14, 2009
    Messages:
    260
    Resources:
    6
    Skins:
    1
    Template:
    5
    Resources:
    6
    @Chaosy

    Yes this is what I suspected but I was unsure.

    @Dr Super Good

    Yes your example is correct. Your explanation made alot of sense. Thanks!
     
  9. EdgeOfChaos

    EdgeOfChaos

    Joined:
    Jan 8, 2014
    Messages:
    643
    Resources:
    1
    Tutorials:
    1
    Resources:
    1
    DRY absolutely applies in JASS. But that example is not good DRY. That is making unneeded functions which is just as bad as repeating yourself.

    Here's a real example of DRY.
    When we need to cast a dummy ability, there are usually 5 lines we use.
    1) Create a dummy unit
    2) Add ability to dummy unit
    3) Order dummy unit to cast
    4) Add expiration to dummy unit
    5) Null dummy unit
    5 lines doesn't sound like much, but if you use TONS of dummy abilities, it'll be very annoying to CnP this every time.
    What I usually do if create a function, castTarget. I make it take the source, target, ability ID, order string. I also create other ones for no-target and point-target if needed. Then I put those 5 lines into that function.

    Now when I use a dummy caster, it is 1 line of code.

    Here's my opinion on refactoring. If you're copy pasting lines over 3-4 length, you should refactor it into a function if possible. I know 3-4 lines does not sound like a lot, but the point is that this really adds up. You'd be surprised how much more readable code is when you can easily read the logic behind it, as you can when each function is kept short with well named method calls.
     
  10. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,804
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    The same situation applies with procedural coupling of function arguments. In your example if you are calling the "castTarget" function with mostly the same arguments in different locations, eg the same ability and order, one can wrap it in another function to remove arguments for those 2 parameters. This way if either were to be changed one just has to change them in that function, rather than every occurrence of those arguments.