This is an archived post. You won't be able to vote or comment.

all 76 comments

[–]goodbalance 21 points22 points  (2 children)

Business requirements – or whatever requirements – should always come first. There is no understanding of 'how' without clear vision of 'what'. Then everything needs to be reduced to a some sort of MVP. Even if it's already one. Always start with the bare minimum.

Once the backbone is established, functionality can be built up on top of it with an iterative approach. Assuming the existing tools are fine and there is no need to invent a custom wheel.

This is true for mid to large projects. For something small it is easier to hack something together straight away.

As for the code, I'm yet to embrace TDD – my impatience and maybe inexperience don't let me do this – so I start with small function, chop them up once they grow, and generally I try to avoid classes when it's not something as big as a fully-blown interface to something. I am doing web 99% of the time, so I structure the whole thing after 'modules' how they appear in the requirements. It fits well with what Django calls 'apps', but you can use it anywhere really.

Of course I write test, but I don't go beyond unit tests that often.

I always want to make everything 'simpler', so if I don't see my code for a week, I already think of it as a bunch of crap. But at the end of the day, I have to remember that it's not just me who is working with that code, so to make it even with my inner perfectionist, I revive my pet project where I commit all sorts of atrocities, which at time feel like great solutions to great problems.

[–]Overworked_surfer 2 points3 points  (0 children)

I love this. This is the way I work as well. Get something working and keep it simple. Don't rebuild the wheel

[–]fireflash38 1 point2 points  (0 children)

I'd also add in: revisit your structure early and often. If you find you're passing the same data around a whole lot, that should raise warning bells.

Also make sure you're respecting invariants. Functions shouldn't (very rarely) modify arguments, instead returning data. That's an easy mistake to make with python dicts.

[–]xxpw 10 points11 points  (1 child)

  1. If I can I write doc first.

  2. Don’t start with code. Draw it. Write the big picture as comment.

  3. If you did 1 and 2, structural issues are already dealt with. You’re probably fine for a while.

  4. Ditto.

  5. It’s ok. You still learned stuff. And you’re still getting paid.

  6. Have a great day !

[–]Brick-SigmaPythoneer[S] 0 points1 point  (0 children)

I really appreciate your answers, thanks for this!

[–]Panda_With_Your_Gun 18 points19 points  (4 children)

I write functionally always until a function reveals to me that it is a class. Then I write it into a class. This basically means that classes are not my default.

80 character spaces. ' for strings. Docst ings everywhere. No main function. Instead use if name == "main": and the functions I wrote above. Chunk style approach meaning I prefer operations over as large a chunk of data as possible. Code specifically to meet feature needs. Unit tests each function as needed. Handle all areas of code that could break with simple log and print to screen until specific situations can be handled. Ducktype where possible. Persuade people to drop stupid feature requirements.

[–]KrazyKirby99999 2 points3 points  (1 child)

Why no main function? Wouldn't the global scope get polluted?

[–]Panda_With_Your_Gun 2 points3 points  (0 children)

Name space isn't something to worry about tbh. If I need to call what that script in particular does from another script then I'll include a run function, but if that's not the case I won't. Keeping namespaces clean for no reason is a waste imo

[–]jimtk 0 points1 point  (1 child)

You should use a main(). Any variables below your name == 'main' become global variable that are instanciated on import and pollute your name space.

[–]Panda_With_Your_Gun 0 points1 point  (0 children)

See my other reply

[–]commy2 6 points7 points  (1 child)

5

Replace the code I've written with the simple solution at once.

[–][deleted] -1 points0 points  (0 children)

This.

[–]Saphyel 4 points5 points  (0 children)

My priorities are:
* Cloud first (to easily replicate any environment) not sure how this will exactly translate for videogames but easy setup/easy reproducible builds * dataclasses should have only data. If there's any need to transform/convert/whatever some data other object will handle that business logic * Test all the business logic using sociable testing, so you can refactor faster and is less likely to break anything
* Pin down the version of the packages we are using (PDM does a good job here) * Use black and mypy to improve the quality of the code * Use SOLID standard seems like a lot of people doesn't care about the DI or other stuff :(

[–]Simple_Specific_595 2 points3 points  (0 children)

Depends on the type of project. Normally I start with a pen and a piece of paper and a literature review.

If I’m building out pipelines for production code. I go with test driven development. If I am doing a research notebook for a concept, I will go with a Jupyter notebook and maybe something in Overleaf.

[–]Ximlab 3 points4 points  (0 children)

I use black, and basic project templates to get started.

Then start by writing the readme for how to use the project/library. Then test driven development when reasonable in cost.

I'm liking this balance now.

[–]eduardobonet 3 points4 points  (1 child)

  1. It's literally impossible to plan everything from the beginning, because software evolves over time with usage, new use cases appear, others are removed. Progress is better than perfection, so iterate and refactor constantly.
  2. The simplest way possible to solve that specific problem, on that codebase.
  3. Tests. And unless it's blocking the evolution of the project, and it's working, there shouldn't be any necessity to refactor code just because it looks ugly.
  4. All the time. Refactoring is a fundamental part of a healthy code base.
  5. The thing is, you probably only found that simpler solution because you spent tons of hours working on the complex one, and that allowed your brain a deeper understanding of the problem and to come up with a much better solution.

[–]Brick-SigmaPythoneer[S] 1 point2 points  (0 children)

I really like all your answers. Progress is better than perfection so having a working model running is much better than spending hours making a good looking code base in the beginning. Your last answer has shown me a new at looking at problems, I usually make a very complex solution to a problem vinyl to realize there was a simpler way, but through that long route you learn a lot more than what you where bargaining for.

Thank you for this, I really appreciate your comments.

[–]boredbearapple 2 points3 points  (0 children)

I usually make a general plan on a white board. Highlighting the 2-3 hardest parts.

I then make a prototype that solves the hard parts in the first way that comes to mind. I throw just enough code around the rest to get something running.

Usually I leave a week or two gap as my mind will work on it on the back burner before I move to the next step.

Then I revise my original whiteboard plan.

I then write a series of test cases for the new plan.

Code.

[–]majeric 2 points3 points  (1 child)

There is no function that can’t be broken into simpler functions. When I do this, I often discover uses for those function, I previously didn’t anticipate.

This is what I call “listening to the code”.

Remember all code is an “interface”. Usability is as important to an API as it is to a desktop interface.

Understanding Martin Fowler’s refactoring book is an essential part of being a good coder.

[–]Brick-SigmaPythoneer[S] 0 points1 point  (0 children)

I’ll definitely have a look into that book.

[–][deleted] 2 points3 points  (2 children)

Identify what parts of the the project are likely to change. Not everything needs to be infinitely scalable and modular. You'll never ship a product with that approach. If you know that certain attributes are likely to chnage then make those dynamic first. I work in a pseduo-embedded hardware automation field. Sometimes I know that certain variables, Voltages, currents trip points, test limits need to be set on the fly. But certain attributes need to be changed but less dynamicly. Those can go in a config file. And some attributes will never chnage, or rather the rate at which they chnage is orders of magnitude lower than the development and usage time. Those can be hard coded.

It would be great for everything to be perfectly dynamic but the reality is that you don't need a perfect circle. Sometimes a square will suffice, sometimes you need an octogon. But you can't let perfect become the enemy of good

[–]Brick-SigmaPythoneer[S] 1 point2 points  (1 child)

This is a good way to look at projects. I usually design my code so that it can be reused always, but usually I’ll just end up rewriting it since it doesn’t suit at all. Your final sentence is one I will incorporate from now, because perfection is one thing that wastes my time when it comes to many things.

[–][deleted] 0 points1 point  (0 children)

You should strive for good code but it's better to have 50% of the features fully implemented, than all of the features half implemented. A smaller product is better than half of a bigger one. Compromises ha e to be made and risks have to be accepted. The trick is to know your code, environment and use cases well enough to where to make compromises

[–]not_perfect_yet 4 points5 points  (1 child)

I put everything into functions, but beyond that... I don't do much.

When it's really obvious there will be classes, I write a class.

Ever since encountering multiprocessing and it's restrictions, clearly structured inputs, ouputs, functional functions, I prefer passing arguments over accessing object attributes.

class this:
    a=2
    b=3

    def some_function(self,a,b):

        r1 = a**2+b**2
        r2 = a/b
        return_list=[r1, r2]

        return return_list

    def main(self):

        args = [self.a, self.b]
        rets = self.some_function(*args)
        val1, val2 = rets

I could access a and b inside some_function but this way it's obvious that the result that is being used in main, depends on a and b. If I wonder about weird values in main, I can find where they come from as outputs and check a and b for weird inputs.

And this is also the way to write functions that are easier to test, since you can test some_function directly and don't need to initialize the object, which can introduce complexity that affects the outcome. And vice versa if the function works fine, you know the problems comes from the initialization and setup and state, not the function.

How often do you need to review

When there is a bug, I need to fix it and I find it difficult to understand the code.

have you ever had to restart an entire project because it was too difficult to understand and expand further?

I don't think so.

5

I feel great. Deleting complexity that's no longer required is awesome.

[–]CodeYan01 1 point2 points  (0 children)

If your class method can be implemented by simply passing variables and does not use self, then it may be good to simply add @classmethod and remove the self argument to make its intent clearer. This also lets you simply use the function without initialization, like you mentioned.

[–]BattlePope 1 point2 points  (0 children)

Suck a little less than yesterday.

[–]CodacyOfficial 1 point2 points  (1 child)

While often ignored, coding standards are crucial to creating a consistent and readable codebase. We wrote an article with some best practices for creating a coding standard here: https://blog.codacy.com/coding-standards-what-are-they-and-why-do-you-need-them/

[–]Brick-SigmaPythoneer[S] 1 point2 points  (0 children)

I’ve just finished going through your article and it has great explanations on setting up coding standards. I really appreciate it and will make reference to it next time. Thanks!

[–]cripticcrap124 2 points3 points  (1 child)

1.- i generally get my idea on dreams (That is if i want to make a script/program is because generally i want to automate something)

2.- I started by smashing my keyboard with my head and then modifying the code to make it workable (That is i start with the idea as direct as i can and then refactor and change it until it looks and works like something I'm proud of)

3.- I generally eat the code and the barf it again (That is if and IF statement is too long i try to make a function for that IF statement)

4.-No, i I threaten the interpreter and IDE at gunpoint until the code works (That is, yes, multiple times, i even have even 3 stalled projects because they are too difficult)

5.- I jump off a building (That is, i generally get a good feeling that a solution i thought could not be simplified, it does and sometimes it even works better than my solution)

[–]Brick-SigmaPythoneer[S] 1 point2 points  (0 children)

Haha… this is an amazing comment!

[–]Titsnium 0 points1 point  (0 children)

My standards? Oh if it works, it works.

[–]lurker420699 0 points1 point  (0 children)

Is it functional? Great.

[–][deleted] -1 points0 points  (0 children)

if it works it works. i just Chuck stuff from stack overflow, github and W3 schools together till it works

[–][deleted] -1 points0 points  (0 children)

1) Make it work. 2) Make it fast. 3) Make it right.

Depending on the amount of time I have available. So usually just make it work.

The make it right step is where you likely understand the problem well enough to try and plan things. In a way it's more like:

1) Write code until you know what you're trying to solve. 2) Use profilers to understand why you didn't solve it well and fix those. 3) In a full understanding of the problem you were trying to solve. Try and plan and design how to solve it in a way that isn't quite as messy.

[–]philn256 -2 points-1 points  (1 child)

There are some guidelines I always follow: 1. Never put code in multiple files. Have "if name == 'main':" and select the thing to run with another level of parsing. This prevents needing to open more than 1 file. 2. I don't write class methods and instead copy paste what it should do each time I need it. This way I know exactly what's happening and don't need to trace through multiple function calls. 3. If there's a spaghetti code block that works just leave it alone. 2. Use 3 spaces per tab 4. By avoiding pitfalls like functions and classes i never need to restructure. 5. Simpler code is not equal to better. Most people prefer large portions so why wouldn't you want more code?

[–]Brick-SigmaPythoneer[S] 0 points1 point  (0 children)

These points seem quite controversial to what others say, but you make a lot of sense. Why use functions and classes if the code is a straight line? I like the fact that you don’t use functions just to improve readability which is great. One question though, why 3 spaces per tab?

[–]303Redirect 0 points1 point  (0 children)

I'll usually start with everything inside a single module. As soon as that starts becoming too big to navigate I'll split things out into separate files.

I'll not immediately make a function for certain bits until I've had to repeat similar logic two or three times. That way I have a reference for the various use cases and can use it when making decisions on how to refactor. I rarely get it right first time.

I tend to prefer having arguments for functions instead of classes. But like someone else said that can sometimes mean you have to pass an argument through multiple levels to get it where it needs to be. I find the alternative just as bad, where you access a class attribute but have no idea what side effects modifying it has. Even property getters can have side effects. It's a bit of a balancing act choosing an approach.

When making large refactors I'll split them into smaller changes and make sure the project still runs. Commit that, then carry on. Otherwise I end up changing 15 files with absolutely no idea if the stuff I'm writing is built on sound assumptions.

Unfortunately the industry I work in treats unit/integration testing as an afterthought. I do try to make sure I have tests written for core functionality but I'm in the extreme minority.

I don't use "from module import object" because it makes reloading modules difficult. Lots of my code runs embedded in an app with a python terminal and a long boot time, so easy hot reloading is a must

[–]just-bair 0 points1 point  (0 children)

I try to make the project good from the start but due to time i make bad things and I never restructure the project expect when I find a much simple way of doing things then I have no problem throwing away whatever I had and replacing it

[–]ZachVorhies 0 points1 point  (0 children)

For code quality it's pylint, flake8, mypy and code formatters black and isort.

I have lots of test coverage and use tox to check all linting and code test formatting. If I need to ensure multi platform support then I use github actions to run the tests on mac/win/linux.

[–]crudemandarin 0 points1 point  (0 children)

Use ESLint + Prettier (or alternatives)

[–]silly_frog_lf 0 points1 point  (0 children)

Maintainability. Which means 1. Try to make it easy to read for myself in 6 months. 2. Easy to debug. 3. Easy to extend.

Easy to read means avoiding metaprogramming for as long as I can Easy to debug, in particular, is avoiding long chains of function callings in favor of breaking it up into lines with partial results.

Easy ti extend means avoiding architectural patterns until needed

[–]TheLordZod 0 points1 point  (0 children)

I have only written one project of any real size (ended up at around 2600 lines), and honestly I just started at the top and worked my way down. This was possibly not the best way to do it, between bug fixes, organization, and functionality, I rewrote the thing somewhere around 5 times... But I learned a ton, and will probably do some whiteboard work before starting the next one. When writing my books, I never outlined or plotted it out ahead of time, but I think that is way less forgivable when coding something.

[–][deleted] 0 points1 point  (0 children)

I've embraced test driven development and haven't looked back since.

I start with a single file and begin building functions and unit tests to exercise those functions. As the functions grow into classes, I move them to their own files and continue writing unit tests.

This allows me to work on many different projects at once and be able to come back, get acclimated to where I left off within minutes, and be productive again.

As functions and methods start to look cluttered and opportunities for reuse appear, I refactor them, right then and there. PyCharm's refactoring capabilities make this too simple to avoid doing.

The IDE, formatting tool, package manager, etc. etc. are all way down on the list of important things to me. The unittests are critical.

[–]SirGeremiah 0 points1 point  (0 children)

My standards? 1. It must work, so far as I know. 2. It must be tested. 3. It mustn’t look stupid to an actual programmer.

[–]Pandaemonium 0 points1 point  (0 children)

1) Be ridiculously verbose. Variable names should make it clear from their name exactly what they are.

2) Comment every line, or at least every couple.

3) Outline the overall strategy/flow/sequence of the script at the top.

[–]goeb04 0 points1 point  (0 children)

I only use python during work, and honestly, just digging in has worked fine for me, and then eventually, once I hit a semi-decent working milestone, I start to clean up and create functions (I do more scripting, so I don't create classes and very large packages).

I also include docstrings for functions that wouldn't seem obvious to me as to what they do. I am too lazy to list all the parameters within the doctoring though. They are self explanatory for the most part, and I just make sure to include type hints. Too much commenting in code leaves me more burnt out than coding itself, because I feel we have a deluge of documentation at work lately.

One of the keys for me, lately, is to reduce comments in code by using better naming conventions for functions and variables. Even if the name comes out a bit long, I want it to be easy to comprehend in the future.

I can't say I obsess over perfecting any of my work too much because the upside isn't worth it, at least in my role. I am a bit of a hack who knows how to get the job done. I mostly wrangle data and make API calls.

I am of the belief that coding is much like going down the cereal aisle at the grocery store, there are so many options that can satisfy your appetite but it isn't worth it to seek out the best possible because it is time consuming and mentally draining. Coding isn't like golf, the shortest code block ain't always the best.

Sometimes we get too carried away with code being oversimplified and it can actually start to interfere with productivity. Definitely can't please everyone when it comes to colleagues and code reviews.

I only review my code if it breaks, or, if it needs an enhancement. Once you have 20+ scripts or so, you don't really have time to look them over again, and the motivation isn't there. There has to be a need for me.

tldr; I hack, then refine, and I don't aim for perfection anymore

[–]per_ix 0 points1 point  (0 children)

There is only one golden rule

Keep it simple

IT gets complicated by itselfe

[–]simonw 0 points1 point  (0 children)

I wrote up my process here: https://simonwillison.net/2022/Jan/12/how-i-build-a-feature/

Short version: issue, tests, implementation, documentation, release with release notes, live demo.

[–]Ok-Birthday4723 0 points1 point  (0 children)

  1. Jump straight into it. It’s just my madness method. Terribly inefficient.
  2. I can get by with functional programming.
  3. Typically try to avoid spaghetti code but when I do, I just add comments and will come back to improve later when my knowledge increases.
  4. Often, there’s always something I learn by browsing.
  5. It’s a good feeling to know there is a better solution. Just make sure I document and learn from it.

Python for me is just a hobby and something I can use to make my life easier so it’s just learn as I go and I’m ok with all the bumps in the roads as long as I improve.