• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Code Abstraction

Status
Not open for further replies.

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
(No sure where to post this, took a guess)

I recently had a code quality course at the university I am attending.
Through it I found some love for abstract code.

The idea of abstraction is to be able to understand how a piece of code operates without actually having to understand what it does.
It sounds confusing but it really is not.

Let's say I should create the following spell:
Deals 100 damage over 10 seconds to a single enemy unit.

Through abstraction it could look like:

JASS:
function onLoop takes nothing returns nothing
    call LoadFromHashtable()
    call UpdateFromHashtable()
    call DamageUnit()
    if IsDurationReached() then
        call FlushHash()
    endif
endfunction

All the details are hidden behind functions, which makes it way more readable and easy to find the part you are interested in.

While it would prevent some local usage, I think it is an underrated approach to code.
Never seen it done in wc3 modding, am I missing something entirely or?..
 
While it would prevent some local usage, I think it is an underrated approach to code.
Never seen it done in wc3 modding, am I missing something entirely or?..

One problem with this in wc3 coding is that a lot of vjass users are concerned with performance more than they should be, so a lot of vjass code is unreadable either by necessity or design.

Also vjass' optimizer isn't that good so a lot of excess calls will stay there. Also jass has really expensive function calls in general, since it is an interpreted language, so when code loops, if it is not inlined, it actually does eat up a chunk of performance.

In my projectile system I have this code.

Wurst:
/** Processes the projectile by moving it appropriately and colliding it with objects
    Returns whether the projectile needs to be destroyed. */
function process() returns bool
    return move() // each of those functions also returns whether the projectile needs to be destroyed,
    or collideWithTerrain() // and processing ends once ONE true is reached
    or collideWithEntities()
 
static function onLoop()
    for projectile from Projectile.staticItr()
        if projectile.process()
            destroy projectile

The function onLoop() is called by a timer. The function process and the functions it call all return a value correlating to whether the projectile needs to be destroyed. This results in a very compact and readable code, and even if someone doesn't know why move() would return anything, they can read the hotdoc which states that it returns whether the projectile gets destroyed.

There are different ways to write this in a more abstract manner (like using some canMove() and shouldDestroy() functions to further detail what I mean), but it is not necessary a lot of the time. Code should be ABSTRACT ENOUGH for the intended purpose and audience, and making it too abstract/reusable without a purpose is often just going to create bigger bloats of code, and ironically make things less readable. Basically, you should strive towards a fitting level of abstraction for your purposes, not overdo it.

Also the concept you should really look into in terms of abstraction are interfaces, rather than just making wrappers for code that explains what it does but not how it does it.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
Ideally all abilities should be abstracted to some kind of extendable ability engine. Such system is then data fed. This means that balancing and implementation logic are kept separate.

I tried a prototype API for someone, and the results looked promising but I was not happy with how cumbersome the API turned out.

Typical features such an engine supports.
  • Binding the same ability logic to 2 or more abilities with different values. Most trigger enhanced abilities only support being bound to a single ability without being duplicated. An example might be a hero and monster version of an ability.
  • Being able to set/customize ability scaling logic from a high level. Most trigger enhanced abilities have very few modes of standard scaling, with anything else require one to alter the ability code. An example might be generically applying HotS style damage scaling with level to all abilities.
  • Being able to re-use parts of one ability to make another ability. Most trigger enhanced abilities have dedicated effects and mechanics that cannot be tagged on to other trigger enhanced abilities directly. An example might be using a chain lightning system and a forked lightning system to create a forked chain lightning ability on top of separate chain lightning and forked lightning abilities.
 
Last edited:
Well, yes. Ideally an ability would be a piece of code, that extends or instantiates some other code that does the act of
  • detecting when it is casted
  • detecting whether it should be allowed to be casted based on a heuristic set by the user
  • creating an easy handler for onCast event set by the user
  • doing on loop processing set by the user (optional)
  • doing the cleanup set by the user
And this is actually really easy to do in wurst, for example, since closures allow you to write concise callback code. In vjass this is also possible but the code is going to be way uglier.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
Your example is GUI in Jass. No access to locals. No.
Putting every 2 lines of code in a function is more mess than not doing so.

And yes, this is basically what students are taught at university from my experience.
The epitome of clean code is to have it all abstracted, or in other words to have no code. Except hiding mess isn't cleaning it - cleaning it is cleaning it.

(site discussion?)
 
Last edited:
Your example is GUI in Jass. No access to locals. No.
Putting every 2 lines of code in a function is more mess than not doing so. And yes, this is basically what students are taught at university from my experience.

Good point, I wasn't even looking at the code.

But in general, this is somewhat true - students are told to create functions that are not reusable whatsoever and put stack them on top of each other so that the code looks like sentences. This is not really what abstraction is about, as I said, abstraction has more to do with a concept like an interface.

So Chaosy, in your case, if one wants to really read what the code does or debug it, they have to go all over the place instead of reading a nice stream of code that doesn't go external from the main function all the time. In jass there's no code design to be had, you just slap things together and hope it works, it's different with vjass/wurst.

Whether something is externalized from the called function shouldn't depend on whether it looks good as much as whether it should be a separate responsibility.

In my example, I have separate functions that all return a value, and have different responsibilities. If you create 10 functions for one responsibility, that is just clutter and pointless overhead, unless you're using a lot of reusable code and externalizing functionality to objects that are actually going to provide the functionality. Your example uses the global scope which is completely anti-everything when it comes to actual code design.

In OOP programming, this means depending on object's methods to perform things they are responsible for. If you're using an interface, this means you don't care which object is bellow the hood, you interact with it as you've defined the interactions in the interface, and this would at that point really be about the abstraction.

Here's an ACTUAL example of abstraction. If you intend to separate the concept of interaction from the implemented functionality, then you want interfaces. The example is meaningless except in its display of how an abstraction is actually used - you pass an object that supports certain interactions, but you don't know its type. And you work with it all the way through, then destroy it in the end.

Wurst:
/** Some kind of an object that can contain and update one object */
public interface Store<T>
    function put(T t)
    function update()
    function load() returns T

public class SomeInst
    protected static constant TIMEOUT = 0.05
    protected Store<unit> store
    protected int ticksLeft
    protected real damageAmount

    function onLoop()
        if ticksLeft > 0
            let u = store.load()
            u.damage(damageAmount)
            store.update()
            ticksLeft--
        else
            destroy this

    construct(Store<unit> store, real time, real damageAmount)
        ticksLeft = time / TIMEOUT
        this.store = store
        this.damageAmount = damageAmount

    ondestroy
        destroy store

So, in the end, what you were talking about is really not as much about abstraction as it is about sensible code design, which is way easier to pull off in vjass (and especially wurst) than it ever could be in raw jass, which has NO STRUCTURE WHATSOEVER, and the "abstraction" you speak of is really about how you divide responsibilities among the objects you work with, you should never depend on the global scope for things like that.
 
Last edited:

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
@GhostWolf
While my example is exaggerated, I am reluctant to believe that having a gigantic(ish xD) loop function with 80 lines is the way to go.
If I am a not-so-experienced coder and need to understand how the code works for some reason, going through that loop function might be quite inefficient.

I do agree that hiding ugly code behind a wrapper is not clean code, but if I have code that can be summarized in a function.. why not?
Since I like to use hashtables, it is often I find myself having 5-6 lines saving values at cast of a spell. Why not put this behind a function, it is unavoidable function calls that can be understood with just a word or two.

@HappyTauren
I used jass as an example since I figured it is the language that most know.
I know a lot of the example I used disappear in wurst and vjass, but the principle is the same.
I have never seen someone use a wrapper like function for the sake of readability. Not saying we should only have abstract calls, but it seems to be gone entirely from wc3 modding.
 
Level 4
Joined
Jan 9, 2016
Messages
42
Your example is GUI in Jass. No access to locals. No.
Putting every 2 lines of code in a function is more mess than not doing so.

And yes, this is basically what students are taught at university from my experience.
The epitome of clean code is to have it all abstracted, or in other words to have no code. Except hiding mess isn't cleaning it - cleaning it is cleaning it.

(site discussion?)

Listen to him.

Abstracting code is the act of making barriers for the sake of some sense of structure. Make too many and you'll find yourself trying to overcome the mess of barriers you've built instead of building the thing you're supposed to be building.

Read this article. There are other ways to make code readable yet flexible enough to make changes without building a whole bunch of interfaces and abstracting functions.
 
Status
Not open for further replies.
Top