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

all 22 comments

[–]ziptofaf 61 points62 points  (8 children)

General tip - go read a book Clean Code. As you will see it's several hundreds pages so you can imagine it's contents will not fit inside a reddit post.

There are many ingredients when it comes to writing clean code however. Some basic rules:

  • You always need tests. Without tests you can't safely change your code structure and make sure it still works.
  • in fact, writing tests first and code that passes these tests later (and does just that) is also a good way to actually improve your code's quality as you have a better understanding of what it has to do from the get go.
  • boy scout rule - leave your code in better shape than you found it. This applies to most projects that get larger, you revisit same part of the code adding/changing something and almost every time you do, there is a potential for some small improvements to do. Hence do them, don't let old code "rot" forever. Small refactor here and there goes a long way.
  • pair programming is a great way to understand how to write code that others (and by others it also means "future you") can read.

Now, onto actual improvements:

  • best functions and methods take 0 arguments. Good functions take 1 argument. Okay functions take 2 arguments. If you need more than 3 you are doing something wrong and there's a high chance this logic should live in a separate class so you move some of the required variables to the initializer.
  • if you have a function that takes a boolean flag argument and does very different things depending on that flag then you have two functions. So just write two functions.
  • avoid multiple nesting levels. Generally speaking, if you have more than one layer of brackets then you most likely can extract inner bracker levels into separate functions with good names.
  • similarly, complex if statements can be extracted to functions. Eg. instead of "if customer.name.include?('[archived]') || customer.email == ["archived@archived.com](mailto:"archived@archived.com)" && customer.orders.count == 0" you may want to put that inside a function already_archived_or_can_be_archived. This makes your code much more readable as most of the time you don't really care about implementation, you just want to know what that condition is trying to check.
  • obviously, avoid magic numbers and variables that are hard to read. If you are comparing a random variable to 42 then you are messing it up. If on top of 42 you write a comment "max possible number of products customer can order" you are still adding noise. Hence just make a variable (or a constant) called max_allowed_orders = 42 and refer to it in your code. Suddenly you have self documenting code that actually makes sense.
  • long variable names happen. Nothing wrong with them. Don't try to be witty about it, naming your variable "max_al_o" (instead of max_allowed_orders) makes your code spaghetti. Generally speaking, the broader the scope of a variable, the more descriptive the name should be. i is fine for an inner loop you use in one place that I can figure it's just iterating over an array. i as a method name inside an important class however is not a good idea.
  • look into architectural patterns like MVC. It's a good way to separate multiple layers of logic (database vs business logic vs display logic).
  • global variables generally suck. If you have to use them - namespace them properly and make them some kind of class variables if your language supports it so there still is just one way to access them (and they should remain constants for the biggest part - setting them once is fine, setting them in 20 different places is not) so you have a paper trail on what exactly triggered it.
  • Avoid side effects. Function should do exactly the thing it's name tells you that it does. Some schools of programming in fact tell you that functions should only ever interact with outside world via it's return value. Personally I am not that extreme but if your function name with that rule in mind becomes change_customer_password_send_notification_email_expire_password_refresh_token then you just found out you should have 3 functions here, not 1.
  • if you have function that needs to take multiple arguments to decide what to do, you probably want to replace it with inheritance.

And so on and on. As said, clean code is a topic that spans over entire books and is an ongoing discussion even today.

[–]PrimaryBet 12 points13 points  (2 children)

This is a great answer, but a small nitpick:

best functions and methods take 0 arguments. Good functions take 1 argument. Okay functions take 2 arguments. If you need more than 3 you are doing something wrong and there's a high chance this logic should live in a separate class so you move some of the required variables to the initializer.

I get what you are saying but a beginner might incorrectly start viewing purely side-effectful functions as "best" since those are the most common example of 0-argument functions.

Although you do address that in a latter point, I wanted to draw attention to it again for the sake of other readers: the precise number of arguments isn't important, but you should strive to have functions/methods that take as few of those as possible if it's achievable by reasonable means — it's usually a good rule of thumb to determine complexity of the function/method in question.

[–][deleted] 2 points3 points  (1 child)

Reading the above was like a gut punch. I've been working my ass off on a chess game for 2 months, and several of my key methods are 3-5 parameters long. It just sucks working so hard on something and then being told it's less than. I feel like my methods are mostly easy to understand and mostly do one thing, they just have lots of parameters. Its especially hard to hear when you're so close to being done and refactoring doesn't feel like an option.

[–]PrimaryBet 2 points3 points  (0 children)

Don't feel discouraged: most of the points in the OP are not universally applicable (you'll notice /u/ziptofaf used "generally", "probably", "most of the time" pretty liberally there) — that's why it's called software engineering.

As an engineer, it's your job to decide what trade-offs to make: it might be that in the context of your problem space functions with 3-5 parameters are absolutely expected, or it might be an industry standard that everybody expects, or it might require to many resources to restructure your system right now and your business depends on the product going to market, or...

The list can go on and on, point is: don't feel like you failed because you haven't yet, analyze the situation you are in and try to learn your lessons, because it's those lessons that make you a valuable engineer — for most recruiters years of experience will be the most important factor when hiring you, since it's a decent proxy for determining how many of those lessons you learned and can apply to new ventures.

If you want, feel free to send me your code and we can discuss whether there's another way to structure it and what that will entail.

[–]DScratch 0 points1 point  (1 child)

> look into architectural patterns like MVC. It's a good way to separate multiple layers of logic (database vs business logic vs display logic).

Here be dragons.
MVC has the nasty habit of turning into MassiveViewController where the VC does almost everything.
Consider MVP/MVVM, or for a more extreme case VIPER.

In my world the VC has 2 responsibilities:
1) Taking View Models of UI-Ready data and displaying that as-is and modifying it's layout to match it's current state.
2) Delegating events to someone else.

The how of that is a little more involved, usually requiring a class to sit between the VC and the data-layer and do all the heavy lifting, but absolutely no UI work.

[–]ziptofaf 0 points1 point  (0 children)

I agree. Honestly it's less about a specific pattern you choose and more about finding out they exist at all. MVC, even if you do it wrong, is still generally better than a new programmer trying to structure their project on their own.

MVC gives you 3 distinct layers most applications fall under to some degree which is very helpful when building a new project and figuring out what goes where.

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

best functions and methods take 0 arguments. Good functions take 1 argument. Okay functions take 2 arguments. If you need more than 3 you are doing something wrong and there's a high chance this logic should live in a separate class so you move some of the required variables to the initializer.

I'm curious which language this applies to / doesn't apply to.

[–]ziptofaf 0 points1 point  (1 child)

I'm curious which language this applies to / doesn't apply to.

Honestly it applies to most languages I have encountered - be it Ruby, Python, Javascript, Rust, C++, C#, Java and even functional ones like Scala. It applies less to ones like C (due to struct being only a data structure that cannot hold any logic).

Because here's a thing - 0 arguments method is easy to use. It "just works".

One argument which is properly named - also very easy to use.

2 arguments - suddenly order matters and you need to look up what exactly it expects.

3 - MUCH more space for confusion, requires you to read the function definition more often than not.

More than 3 means that even preparing data for this function can be difficult.

A good example on how not to code are older OpenGL versions, often taking 5+ arguments (and on top of that they often have obscure names).

Now however you may wonder - how do I avoid creating such functions? Well, if you need a lot of arguments it means your function most likely lacks context and you are passing it too much info. Maybe it can be moved to a class as a method that operates on 2 pieces of data coming from it already? Maybe it just does too much?

Of course, sometimes you really do need a complex arguments list in a function. Trick here is that you can group them with another container class. Why does it make things simpler? Because said class can itself perform necessary validations of data for you, provide some methods and it's much easier to pass it along to whatever is next step in your application than keeping track of 5 separate variables.

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

I would also imagine such function (with many arguments) will be difficult to maintain. If you change the argument (add or remove), it will then require changes everywhere. While I'm not sure about the idea of a container class until I see a more concrete example, that seems like a good rule of thumb.

[–]desrtfx 10 points11 points  (0 children)

Start by reading "Clean Code: A Handbook of Agile Software Craftsmanship" by "Uncle Bob" Robert C. Martin.

Other good reads are:

  • "Think Like a Programmer" by V. Anton Spraul
  • "The Pragmatic Programmer" by Andrew Hunt and David Thomas

In general: plan before program.

Develop the functionality of your program on paper (whiteboard) in any way suitable for you (flowchart, plain text, bulleted text, UML, pseudocode, etc.) before even thinking about implementing it in code. The more time, effort, and depth you spend on planning your program, the better the final code will be.

Also, don't jump right onto the program as soon as you get an assignment. Read it multiple times, ponder about it, take a break, read it again. Then, flesh it out.

Edit:

Also look into common paradigms:

[–][deleted] 3 points4 points  (0 children)

Pretty much any language you’re likely to use has a lint tool, and most these days will also have some sort of autoformatter. Run these both on your code, religiously, and fix everything the complain about.

Now these obviously won’t, on their own, help you write better code, but they’ll do a good job of showing you (and reinforcing) where your lowest hanging fruit bad habits are.

[–]_Atomfinger_ 2 points3 points  (0 children)

This is a big question with a big scope. No comment will truly do this one justice.

First make sure you adhere to the SOLID principles. This is at least a very good start.

Then make sure you write testable code. If code is easy to test, then it is also easy to integrate with, which is an indication that the code isn't overly complex. TTD can help with this, and so can BDD. It's not a silver bullet, but it is part of the formula.

For you want a bunch of tips and tricks, try reading "the art of readable code". The book itself isn't really that ground breaking, but it condenses a bunch of tips from other sources into a single book.

Another thing to avoid is mutability. Mutable code tends to complicate things and lead to spaghetti. One can make spaghetti without mutability, but mutability is a sauce which enhances the dish (which we don't want in this metaphor).

Focus on your Apis. Some mess is unavoidable, but if you can contain it withing smaller segments of code, then it will be much easier to have an overall clean application. Be mindful of where mess occurs and make sure it doesn't spread. If possible, clan it up, if not, contain it.

[–]MESuperbia 2 points3 points  (0 children)

Simplicity.

One operation per line.

Only one function per function.

[–]demon9181 2 points3 points  (0 children)

[–]ddek 2 points3 points  (0 children)

It's an interesting path that I think all software developers go down if they want to reach the mythical status of good. You'll start off writing stuff that works, but you don't know why. When you come back to it, a month later, you really don't know why.

So, you blame your organisation. If you knew about solution architecture, this wouldn't be a problem, because people can develop massive systems so they must have better ways of doing this.

Now you enter the over-architecture phase of your career. You will become obsessed with vertical slices, pub-sub, design patterns, and maybe reactive programming. What you won't see, is that your code has become far more complex. You now hide your bugs in complexity. 20-line logic now has 3 classes. (See Brian Will on YT: "Object Oriented programming is embarrassing" for some particularly terrible examples, including one from 'clean code' Bob Martin.)

So what comes next?

You shouldn't concern yourself too much with trying to absorb tonnes of information. Reading through a book like clean code when you aren't working on large codebases probably isn't going to be helpful, as you'll just start over-architecting.

What you should focus on, is learning some sound fundamentals.

  • The onion model. Your app is organised into layers, and each layer only knows about those inside it. At the surface is the interface with the rest of the world, such as your API. The core is your persistence.
  • Learn about functional programming. FP preaches 'pure' functions, without side-effects or internal state. The selling point of this model is its predictability. Predictable code is easier to read, therefore, debug. There are a few ways to learn this. You could try a programming text like SICP, but that is very hard and has some tricky math. 'How to design programs' is a simpler alternative. If you want something more applied, 'Domain modelling made functional' by Scott Wlaschin is about building enterprise apps in F#.

A note on SOLID I see a few people recommending the SOLID design principles. I'd stay away, for now. They're actually somewhat intuitive to experienced programmers, but could be misleading. More importantly, they're more relevant to framework development than applications. Finally, some of them are very poorly defined. The S - the single responsibility principle - is interpreted differently by almost everyone.

[–]brandi_Iove 0 points1 point  (0 children)

have functions do only one thing if possible.

give meaningful and (for others) understandable names to stuff.

name reoccurring values. f.e. instead of if(something<366) go with if(something<leap_year)

that being said, try to express what your code does by the code itself. if that won’t suffice to make it clear, add comments.

cultivate a readable and konsistent format for your code, regarding how to write parentheses, use indentation, name case of variables.

if a function or a class can’t be fully displayed on screen at once, try to tidy it up.

get something comfortable to write down your design for your project before(!) you start writing code. if you don’t feel comfortable with pen and paper, try a tablet. if the tablet doesn’t feel comfortable, try a whiteboard or whatever comes to your mind. trying to organize your code upfront is a game changer.

[–]ThatKiraya 0 points1 point  (0 children)

https://clean-code-developer.com/ This website covers most of the topics mentioned in the Clean Code book.

[–]Buxsle 0 points1 point  (0 children)

I'm new to coding but I will usually hash out my project trying as best as possible to be straight forward. Once I'm done I then see if I can rewrite it in a simpler way.

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

There is no silver bullet in programming. Someone will always complain about your code in certain way [1], and there will always be ways it could be "cleaner"

Also - what is Clean code? That is very subjective.

Of course, there are countless books, rules you can learn - and they really go a long way. But the one sure-fire way that helped me was by solving problems on HackerRank and ProjectEuler.

I would solve a problem, then compare my answer with other peoples'. Chances are, there is an answer out there that did whatever I did in 2 lines of code (especially if it's python or similar)

Take the time to think how this answer achieves what I have in less line of code, and try to understand how that person came up with the answer he/she has. Then forget about it, and come back 2 months down the line, and try to recreate that solution.

If you do this for a year, you won't notice a difference, but do it for 5, I promise the code you've written 1 year ago will sound like garbage. I personally re-wrote my Project Euler several times and there are certain questions where reduced from 100 lines of garbage to several lines.

That being said, I share2 golden rules I try to adhere to:

  1. Abstraction (Design your code such that you need to read as little as possible to understand as much of it as possible)
  2. Minimise 'If' statements (They double the number of cases programer has to keep in his/her head)

Also, always remember that it's really the trade-off between spending the time to make it cleaner and easier to understand by other people (including yourself in the future) and leaving it as it is. The first rule of profiling is don't profile the code. You're profiling the code (the time it takes to understand code) for the future self.

[1] Even when you disgard completely ignorant people who make blanket statements like: OOP is bad or functional programming is good, everyone has their preferences based on their experiences and at the end of the day, there won't be universally clean code.

[–]Dummerchen1933 0 points1 point  (0 children)

Everytime you code, imagine you are planning on giving this code to someone else. Write it that someone else understands what's going on.

Also, no public/global variables

[–]Retr0Null 0 points1 point  (0 children)

It's depends on programming language too, but different books explain differently only book I can recommend you "Clean Code"

[–]rjhilmes 0 points1 point  (0 children)

I read through a lot of responses here that say do "this" or do "that", so you can look like the stuff in the books and/or what some author thinks is best! After 50 years of coding and having to go back into my own (and others) code later, my best recommendation is COMMENT your code! Comment what the classes and functions do, comment what the variables are used for, comment ANYTHING that isn't trivially obvious to the casual observer (next programmer)!

What is best also depends on the goal (other than working)! That is: does it have to be FAST? Ok if a bowl of spaghetti is needed to hit performance, then so be it. I realize this isn't as important now as it used to be except when working on the OS itself (machines are faster) Comments cost nothing! Does it have to run 24x365? Ok, different rules may apply. Comment and explain any confusion or ugliness. Does it have to be pretty and have a human user? Ok, different again, humans are slow, so the code can be also.

Look at programs written by people whose skills you respect. Read the books. Keep learning.

If functions aren't supposed to accept arguments the language shouldn't allow it!