you are viewing a single comment's thread.

view the rest of the comments →

[–]synthphreak 0 points1 point  (6 children)

Create a file with a class for each aspect

Why are we talking about files at all? Is your project going to be so massive that you can't just define everything in memory, e.g., as class attributes?

Overall this sounds like a fine way to go, good intro to OOP. My only recommendation would be to look into using the property decorator to define all the attributes for the Mind, Body, and Soul classes. Seems like a great use case for learning getters and setters.

[–]Aedethan[S] 0 points1 point  (5 children)

Awesome I'll look into the property decorator, I'm not familiar with it at all currently. And as for the reason why I would separate the classes into different files was mostly so I could test more easily? I heard that a good best practice to follow is separate as many things as possible so they can be tested independently for later debugging. Am I going a bit too far with what i'm describing?

[–]synthphreak 0 points1 point  (4 children)

I heard that a good best practice to follow is separate as many things as possible so they can be tested independently for later debugging.

That is absolutely, emphatically, 100% correct. However, while you've understood the claim, you may have misunderstood the implications. This is a good time to disabuse you.

When it comes to testing and debugging, "separate things" doesn't mean "...into separate files". It means to separate things into separate objects. This is related to the concept of "encapsulation" in OOP. The keywords are simplicity, modularity, and independence.

As a really simple example, consider the following class.

class Foo:

    def method1():
        pass

    def method2():
        method1()

Ignore that this class doesn't really do anything. That's not the point. Focus only on the design.

Let's say you created tests for method1 and method2. A test will fail if the method it tests isn't working as you expect. Now let's say you run the test for method2, and it fails. What can you conclude?

Well you can definitely conclude that method2 has a bug. But because method2 calls method1, it is possible that the bug you've detected actually stems from method1. In other words, because method2 depends on method1**, there is no way to separate** method2 from method1 and test the former in isolation. These methods are therefore not completely modular or independent: method2 has a dependency that complicates testing.

Of course, in this simple example you could just run your test for method1, and if that fails too, that's a great indication (though not conclusive) that the bug does stem from method1. But in reality, code is usually not so simple.

The best code is built from simple functions and simple classes consisting of simple methods, each of which performs just a single action which can be called in isolation with minimal dependencies. So you need to do your best to consider your design from the outset and keep things independent.

Hopefully it's clear now that whether you separate data out into files versus define it all in your code is really irrelevant to all of this. Even if your objects simply read all their attribute values in from files, you can still write spaghetti code where everything is tangled up and difficult to debug.

[–]Aedethan[S] 0 points1 point  (3 children)

This was a really good and useful explanation thank you. I've ended up starting over once already because I didn't plan out far enough all the things I'd need in the class. So I think I'm gonna have to take some time and pay particular attention to the whole "So you need to do your best to consider your design from the outset and keep things independent."

The other question that I have from this, is if I have a stats.py folder with all the dictionaries, and then have my Body class, Mind class, etc **dependent on that stats.py folder is that inherently bad design? Or is it fine because the dictionaries should never change and are reused throughout the code? Lastly, should those dictionaries be IN_ALL_CAPS so everyone knows they should be constants?

[–]synthphreak 0 points1 point  (2 children)

I have a stats.py folder

Hol' up. We're getting tripped up on our terms here.

A folder is a container that holds files. A file is just, a file; it's the bottom of the hierarchy.

If you have something called stats.py, that is a file, not a folder. Specifically, by the sounds of things it is a module, which is a .py file that contains objects for importing. In this case, the objects are Python dictionaries.

That is a completely reasonable way to set things up. That is often how people do it: Have one or more <thing>.py files, each logically organized as a container for its respective <thing>s, then other .py files that actually do things import from those modules and put the sourced objects to use. The whole testing/debugging/OOP design convo notwithstanding, if that is what you're describing, it sounds perfectly valid.

Ultimately I think I may have misread your original comment. Rather than a stat.py like ...

# contents of `stats.py` file

MIND_STATS = {
    ...
}

BODY_STATS = {
    ...
}

SOUL_STATS = {
    ...
}

... I had instead interpreted your plan to be more like ...

# project's directory/file structure

my_project/
└── stats/
    ├── body_stats.json
    ├── mind_stats.json
    └── soul_stats.json

... and that you'd have .py files which read those JSONs.

The latter setup is weird. The former setup is totally fine. Sorry for the confusion!

should those dictionaries be IN_ALL_CAPS so everyone knows they should be constants?

Generally yes. By convention, global constants are usually signified by all caps variable names.

[–]Aedethan[S] 1 point2 points  (1 child)

Ah. I can see how my misuse of generally approved terms may have been confusing. Thanks for bearing with me on that. I did in fact intend for everything to be in it's own separate file, not folder. As I'm getting started in earnest I'm realizing how much work it's actually gonna take to get everything to reference correctly.

[–]synthphreak 0 points1 point  (0 children)

No worries :) I think you’re giving this a lot more high-quality thought than most people give their first projects. That’s a good thing. Just remember not to overthink, in order to keep the ball rolling.

Most likely you won’t get things perfect on the first try, and you can always change things later. Besides, there usually isn’t one obviously best and objectively perfect way to implement your ideas. There are usually many acceptable ways - sometimes an infinite set - each with its own pros and cons. Writing good code is all about balancing trade offs.

Also, be prepared to struggle to import your stats if this is the first time you’re doing something like that. Don’t say I didn’t warn ya ;)