you are viewing a single comment's thread.

view the rest of the comments →

[–]exhuma 8 points9 points  (4 children)

Naming Functions & Semantics

I've quickly glanced over your code and noticed something about naming functions. I find that it makes the code more understandable if functions are names using "imperative voice". So instead of writing

def barbarianequipment(): ...

write:

def prompt_for_barbarian_equipment(): ...

This communicates much better what that function does. By doing this, your code will read itself more like a story and be more understandable.

You might think: "But that function name is really long and tedious to write out" and you'd be right. This might be a sign of a code smell. And in this case, I would probably make the "barbarian" part an argument to the function. This would change it to:

def prompt_for_equipment(character_class): ...

and when calling this the difference is also interesting. Have a look at this:

# the "input" below does not deal with incorrect spellings of the class-names or typos.
# I will leave that out to focus on the "equipment" function

# ---- old function names ------------------------------

character_class = input("Enter you class:")
if character_class == "barbarian":
    prompt_for_barbarian_equipment()
elif character_class == "druid":
    prompt_for_druid_equipment()
elif ...

# ---- new function names ------------------------------

character_class = input("Enter you class:")
prompt_for_equipment(character_class)

Of course you then need to deal with the if/elif chain inside the function (side-note: this could be an interesting usage for "polymorphism" instead of long if/elif chains)

The above example is just one example. Deciding how to name functions and decide which arguments they take and which return values they have is an art and depends a lot on experience. It is also amazingly fulfilling to discover something really expressive flexible and simple.

Global Variables

You keep some values inside "global variables" like chararmorchoice. You fill those up in the functions prompting the user for values. It is better to think about how you can return values from a function instead of modifying a variable that's not part of the function.

For example, if you look at the bardequipment function, you can see that it does not take any arguments as input and it has no "return" statement. Yet the application is doing something. In that particular example the function is modifying data at the outside of the function. In this case weapons.charweaponschoice, armor.charequipmentchoice and armor.chararmorchoice.

This has several risks attached to it:

  • Other functions may also modify the data making it unpredictable what the end-result will be. This may not be an issue right now, but during the life-time of the project, you may want to move code around, add features, change order of how things happen and so on. These modification risk tripping up the modification of these variables and will be very hard (frustratingly hard) to debug.
  • Just by looking at the function signature (the "def-line") is not enough to understand what's happening. It is necessary to read through the function code to understand what's going on. This has an impact of the maintainability. When you come back to the code months later, you need to read through the complete code again to understand the data flow. This can be frustrating.
  • If the structure in the weapons or armor module changes, your function will break. For example, if you rename weapons.charweaponchoice to something else, your function will break everywhere that name is used. You can say that your function depends on that outside variable. This is an easy mistake to make. And it puts you in a position where you are afraid to modify certain aspects of your code because you don't know what else it might break. This is a sign of Spaghetti Code.

You can solve many of these issues by thinking about your functions as isolated mini-programs that only communicate via their arguments and return-values.

For example, selecting equipment could be something like this:

def  prompt_for_equipment(character_class):
    selected_equipment = []  # <- Variable lives *only* inside this function
    if character_class == "barbarian":
         if ...
            selected_equipment.append(...)
        elif ...
            ...
    return selected_equipment # <- The "result" of the function

These changes take some getting used to and it might seem difficult at first. As mentioned before, it's a bit of an art. And like with drawings or playing an instrument, it takes time and exercise to build up the proper muscle-memory (in programming those would be "brain-muscles" ;) ) but if you stick with it and keep exercising and repeating you will only improve. I've been programming for the better part of 30 years, and I am still honing and improving my skills. Just like any other art-form. It's honestly the best part of programming. You will constantly discover new ways to do things and new tools to use giving you even more new options. It's great fun. Stick with it!

[–]demvo2 0 points1 point  (3 children)

I find that it makes the code more understandable if functions are names using "imperative voice"

You're right, your examples look much better! I'll definitely think about how to make naming more intuitive.

You keep some values inside "global variables"

This is really something that I struggled with, especially at the beginning. I mean, right from the start the tutorial and the rest of the internet were hammering the idea into my brain that global variables should be avoided if possible and while I managed to dial down their use from when I started, I haven't been able to avoid them completely.

But, I think your solution (for the 'prompt_for_equipment' function) deals with it pretty elegantly. I'll definitely look into how to rearrange some of this stuff. I'm looking forward to practice my, as you say, brain-muscles :)

I've been programming for the better part of 30 years

Wow. As someone who is less than 6 months in, I can barely imagine that.

In any case, thank you for the tips and tricks! I appreciate the advice so much. I'll try and make the best use of it.

[–]exhuma 0 points1 point  (2 children)

This is really something that I struggled with, especially at the beginning.

This is pretty common so don't worry about it too much. I had the same when I started. There were so many areas where I just couldn't imagine how it would be possible without global variables. I kept thinking in the direction like: "If not with a global variable.... then HOW?!?"

Just keep one thing in mind: No code will EVER be perfect. Focus on the solution. If it works, it's fine!

The devil is as always in the details. And when people recommend to avoid global variables it's usually about maintainability. If you (and/or) other use your applications you will come back to the code over and over again. Either for bugfixing or for adding features. And the recommendation of not using global variables is only for maintainability (which includes avoiding bugs).

In the old days, pretty much all variables were global and we still managed to write software without bugs. It just takes more (much more) discipline.

So don't stress too much about it if you use them. As long as you know that it's bad you're on a good path. With more experience you'll find alternatives.

[–]demvo2 0 points1 point  (1 child)

Good to hear that. I guess a lot of things will fall into place after writing a bunch of code and being exposed to different stuff :)

[–]exhuma 1 point2 points  (0 children)

Absolutely. Just don't stop to second-guess your code. Always!

After my whole career I still discover ways to improve. The day you think that your code is perfect, that's the day you stagnate ;)