This is the style guide we used in our startup Futurecam.
⦿ Don’t solve problems you don’t have yet out of a desire to have code that’s extensible, testable, reusable or <insert other cool-sounding adjective here>. You’ll end up solving different problems from the ones you’ll actually have, and ending up with bloated, hard to understand code for no benefit.
⦿ Don’t repeat yourself. If you find yourself writing the same thing twice, even if small, refactor. For example, named constants. I’m not religious about them, and don’t mind an inline constant like:
button.borderWidth = 2
This is actually smaller and more readable than:
private static final let BUTTON_BORDER_WIDTH = 2
button.borderWidth = BUTTON_BORDER_WIDTH
So, I don’t use a named constant. But if I create multiple buttons with the same border width:
button2.borderWidth = 2
button3.borderWidth = 2
button4.borderWidth = 2
I create a named constant for it.
⦿ Try to keep functions functional — return a new value rather than updating the class or struct in place.
⦿ Encapsulation: everything should be as private as possible. If someone should be able to read a variable but not write it, make it private(set). Or if people just need to know whether something is zero, but they don’t need the exact value, make it private and add an isZero computed property. As another example, if a named constant is used in only one file, can it be fileprivate?
⦿ Prefer composition over inheritance. If you’re subclassing a class, but not overriding its methods, or passing an instance of the subclass to someone who expects an instance of the superclass, it shouldn’t be a subclass. Maybe use an extension or wrapper class.
⦿ If you coded up something, changing multiple classes to make it work, and you later realise that there’s another way which requires a change in just one class, redo it. Optimise for readers, not yourself. Because unnecessary changes confuse people who understood the earlier way of doing it. It makes debugging hard, too: did the bug happen because of the unnecessary change?
⦿ Use unsigned ints for values that should not be negative, so that classes you pass this to don’t have to think about how to handle this case.
⦿ Don’t have global mutable variables.
⦿ Unit testing makes sense only in some cases, not as a general practice.
⦿ The Law of Demeter is a good code smell: classes should talk to their friends, not strangers. Put differently, don’t have multiple dots in an expression, like a.b.camera.takePhoto(). When you see this code, ask yourself: Does a need to expose the entire object b, or can it expose or forward just one function? Does b need to expose the entire camera, or can it expose only takePhoto()?
⦿ Naming is critical: Spend time trying to find the right name for every class, function or module. Describe what it does to an imaginary listener and then convert it to a name. For example, if you find yourself saying, “This class configures the camera for optimum quality”, CameraConfigurator can be a name.
⦿ Booleans should have “is” or “has” in their name, to prevent a doubt whether file.open opens a file or tells you whether it’s open.
⦿ Don’t use negatives like isUnsuccessful. People have to write if !isUnsuccessful, and double negatives make it hard to understand. Rename it to hasFailed, to avoid the negative. Or change it to isSuccessful, but then you have to go through and make sure to interchange true and false at all call sites.
⦿ When you name something, think about how someone may misinterpret it. For example, does videoResolution mean the number of pixels, width or height? It can be any of these three things. Instead, pick one, and call it videoWidth, videoHeight or videoPixelCount.
⦿ Classes and methods should have documentation saying what they do, and what the caller should do before calling it. “If you do ABC then I’ll do XYZ for you.”
⦿ It’s also important to document what a class doesn’t do. Such as an Order class in an e-commerce app saying that it doesn’t charge the user. These define the boundaries of the class, and really help understanding.
⦿ But don’t write useless documentation. Such as adding a comment for setFoo() saying “Sets foo”. Documentation should say what’s not already obvious from the code.
⦿ If you encounter some undocumented limitation with APIs you’re building on top of, where you try doing it one way, and it doesn’t work, and you try some other way, and it works, add a comment saying what doesn’t work, and why. Does it crash? Is it slow? Does it produce the wrong output? People shouldn’t have to re-discover these the hard way.
⦿ Use TODOs to track minor fixes and improvements that wouldn’t make sense in a Trello task because they require context about the code. Such as: TODO: What if buffer is nil?
⦿ Think twice before writing separate documents like Google docs, since nobody will go back and maintain them. The time taken to write them becomes wasted, and wrong documentation can even confuse people, thus having a negative value. Short, contextual documentation, like a paragraph for each class, is more helpful.
⦿ Your code should be simple, readable and understandable. Other people should be able to read the code for any one class without knowing all the other classes. If they have to ask you, it means it wasn’t clear.
⦿ If something surprised you, refactor so that it doesn’t surprise the next person.
⦿ If a method or class is too complex to understand, split it out. Every class should do one thing. Describe what the class does to an imaginary listener. If you find yourself using the word “and” such as in “This class records videos and saves them”, it might be a violation of the single responsibility principle.
⦿ One code smell is using the word “manager” in a class name, like VideoManager. Better to have a VideoRecorder and a VideoSaver, which are responsible for recording and saving videos, respectively.
⦿ But only if it’s actually hard to understand or describe. Unless there’s a problem, don’t follow design guidelines for the sake of following them. Put differently, if you refactor something, and it’s not better, the refactoring isn’t helpful. Design guidelines are guidelines, not rules.
⦿ Every class should have its members grouped in order of decreasing visibility. People who just want to use the class shouldn’t have to go through implementation details, only the interface of the class.
⦿ Reduce nesting by using early returns. It’s easier to understand one case at once rather than thinking about multiple combinations simultaneously.
⦿ Refactoring is critical. If you’re not doing it, your code is bad.
⦿ If a problem keeps occurring, that’s a sign that you should look at the design to see if it can be fixed at a more fundamental level.
⦿ Refactor incrementally. Identify one small problem, fix it, commit, push and then move on to the next. If you try to do too much, you’ll cut corners, make mistakes or end up with a less clean design than you would have if you focused on one problem at a time.
⦿ Add assertions. I add them even when I think they won’t fire. I’m often wrong, and they do fire, alerting me to a problem I wouldn’t have discovered otherwise. And when debugging, I can be confident that the assertion holds, so I look at other areas, rather than getting confused thinking of many possibilities at once. Our brains have limited capacity, and we need tools to reduce the complexity of the task at hand so that it’s tractable. Assertions also serve as documentation, and raise an alert if someone modifies the code later and unknowingly breaks it.
⦿ If you’re not sure whether an assertion you’re writing is true, write it anyway, and see. If it triggers, you’ve learnt something about the code. If it doesn’t, it serves as documentation of the code, and prevents unintended changes later on.
⦿ When you write a comment, like “the discount percentage should be between 0 and 100%”, convert it to an assertion, because the language will automatically enforce it for you.
⦿ If a function requires something to be a certain state before it’s called, add an assertion. Suppose you have an applyDiscount() method on a cart, and the rule is that you can apply only one discount. Then add an assertion at the beginning of this function that discount_applied is false.
⦿ If a function is expected to leave things in a certain state after it’s done, add an assertion. Say you have a business rule that you can’t apply a 100% discount; the customer needs to pay something. Then assert at the end of applyDiscount() that order_value > 0.
⦿ If you’re debugging an e-commerce app and suspect that a bug is happening because the order value is negative or zero, add an assertion that it’s > 0, and re-run the app. I do this because I’m lazy. I don’t want to think about whether it’s zero if I can just re-run the app and rely on the language to tell me. The right kind of laziness helps. If the assertion triggers, you’ve already made significant progress towards diagnosing the problem. Then add assertions at the caller, then its caller, and so on. Eventually you’ll find the root cause. Commit all the assertions to Git, so that if the problem occurs again, someone else won’t have to spend a lot of time debugging it again. That way, the code has gotten better as a result of your debugging, in addition to the bug being fixed.
⦿ Each class should use assertions to ensure that it works correctly even if other classes have bugs. For example, suppose you have a Camera class that takes a photo when the user presses the shutter button, along with playing a shutter sound. If the UI code, which is in a different class, has a bug, it’s fine for the Camera to not take a photo when the user has pressed the shutter button, but it should never take a photo without playing the shutter sound. Or, for that matter, play the shutter sound without taking a photo. The Camera class should work correctly even if other classes are malfunctioning, like watertight compartments in a ship.