Applying These Six Principles Will Make You a Rockstar Developer
To me, programming is more of an art than it is a technical skill. That might sound interesting to you, but there’s a reason why I feel this way.
Art is very much an expression of a person’s creativity. A great painter can take a blank canvas and turn it into anything in his imagination. A sculptor can turn a stone into a masterpiece. When an artist begins a piece, there is a limitless amount of options available to them. Each stroke of the paintbrush or each chip away at a piece of marble contributes to the overall product. A work of art.
Programming is very similar. There are so many ways that a program can be designed, architected, and built. There’s a vast amount of languages, patterns, and architectures to choose from.
Great programmers are able to make the decisions that result in “beautiful” code. We’ve all seen it. Code that just looks beautiful. It’s clean, it’s simple, it’s easy to understand, it’s easy to make changes to, it’s readable.
Developers who consistently produce this kind of code, are coveted. They’re greatly appreciated on any team, and any developer who contributes to their codebase, later on, is filled with gratitude.
So, how can you pump out code like that? How can write code that is not later considered technical debt? How can you improve your artistry of programming?
Keep It Simple
Simplicity is probably the most important aspect of great code. Everybody values simplicity, and we speak all the time about how simple code should be. But how can you ensure that your code is not overly complex? I use the word “overly” because as your codebase grows, its complexity will increase with its size, which can’t be avoided. But the goal is to keep things as simple as possible. You can achieve this by keeping the following reminders at the forefront of your mind.
Try To Avoid Large Objects and Functions
The larger an object or a function is, the more complex it is. As the responsibilities they carry increase, so does the complexity. Dividing large chunks of code into smaller chunks makes things much easier to manage.
I’ve heard some people say that your functions should not be more than 25 lines. I don’t believe a rule should ever be set for something like this. Instead, think of things as a matter of responsibility.
You want your objects and functions to be responsible for one thing. For example, look at the following pseudocode.
In the code above, three tasks are performed.
- The table view is updated
- Initial data is retrieved from the server
- The main view is updated
That’s fine, but the code is going to be put all into this one method. You can do it that way, but the following way would be much better.
See in this example, instead of writing the code inside the viewDidLoad method, the code itself is moved to other functions. What is the advantage of this?
When reading this code, I understand exactly what happens in the viewDidLoad method. If there’s a bug, I can probably pinpoint what function I need to look inside to discover what the issue is. Also, when I need to go back here to make a change, I’m going to know where I need to look immediately, as opposed to having to read through a bunch of code to discover where I need to make my changes.
This example illustrates the point I’m trying to make. Try to share responsibilities, instead of cramming them all into one place.
Name Things Properly
This is the easiest way to write great code, but because it’s so simple, people often overlook its importance.
You have to name things properly. If you don’t, your code will be viewed as overly complex. Here are some things to keep in mind about naming.
The name should be descriptive
The name should be descriptive enough to where another coder will have a general idea of the object or function’s purpose without having to dig deeper.
And remember, names can be long if that’s what’s necessary to describe the purpose.
For example, there’s nothing wrong with naming a function GetMessagesForUserWithIdFromServer.
When I look at the name of that function, I know exactly what it does. I don’t need to look at the contents to understand this. The quicker a developer can understand what the code does the better.
Avoid abbreviations that are not common.
Using the abbreviation ‘vc’ for a View Controller is fine in Swift because everyone knows what that means. But let’s say you have a class called CommentMessageHandler. Which name would be more appropriate?
- cmMessageHandler — What does ‘cm’ even mean? You might know that ‘cm’ stands for ‘commentMessage’, but I wouldn’t know at first glance.
- commentMessageHandler — This name is better because it’s very evident what exactly this property is.
Proper naming reduces the amount of time a developer requires to understand your code.
** With IntelliSense as good as it is today, abbreviations don’t make a whole lot of sense anyway.
Names should be natural
Names should be somewhat obvious fits for whatever the task is. Often times a developer will have to search your code to find where a specific task is being carried out. It can be tough when the name isn’t a natural fit.
For example, say you have a function responsible for searching for a user, the natural name would be something like searchForUser().
However, if in your UI there’s a button that says “Find User”, would naming the function searchUser() still be appropriate? No. Now you should name the method responsible for this something along the lines of findUser().
Why? Once a developer sees you have a button called ‘Find User’, naturally, he’ll think there’s a function somewhere that’s something along the lines of findUser(). So naming the function searchUser() is going to make it harder for the developer to find that function. That extra time searching is unnecessary time. Therefore it’s technical debt.
Have a Single Source Of Truth
This is extremely important, and not common amongst new developers. Having a ton of objects that can be edited in many different places is asking for trouble.
Remember, as codebases grow, so does the complexity. Small changes will have larger consequences depending on the size of the application. Making a change to a property might seem small at the beginning but could have vast consequences later on.
By having a “single source of truth”, or only one area allowed to make edits to a specific data element, you reduce the risk of harmful side effects.
This is one reason why global objects are so dangerous. They can be accessed from anywhere, and if anyone is allowed to make a change to this object, you open yourself up to a sea of potential issues.
Let’s look at an example. Which of these classes do you think is better, and why?
Isn’t Example 2 the better of the two? Imagine the potential bugs if a developer could set the age while not updating whether the user is a teenager. This would almost certainly cause problems down the road. It would be so easy to forget to always set that isTeenager property to true or false when updating the age.
By having a Single Source Of Truth, we ensure that every time the age is set, the isTeenager value is updated if necessary. That reduces bugs, which reduces wasted time, which reduces technical debt. Other developers will love you.
Keep Things Modular
Modularity is very important. As the codebase grows, the more modular your code is, the easier it will be to make adjustments and additions.
The code is composed of many different code modules that are developed separately. This allows different developers to take on discrete pieces of the system and design and implement them without having to understand all the rest. But to build large programs out of modules effectively, we need to be able to write modules that we can convince ourselves are correct in isolation from the rest of the program. — cs.cornell.edu
When I think of modularity, I think of a car. A car is composed of many different components, all of which can be taken out and replaced. The engine, a component in itself, can be broken down into smaller components, which can be inserted into another engine.
That’s the goal that you want with your code. You should be able to “test” different components in isolation to make sure they work as you expect.
One way that I do this is I try to only allow what I call, “a few points of entry” to my objects. What do I mean by that?
Well, an object can contain many functions and many properties. We know that private scope means you can only access the property/function from within the class. So I try to make most of my properties and functions private. Then only have one or two public properties and functions.
Why do I do this?
Well, if my functions and properties are almost all private, that means that almost all the work being accomplished is being done solely from within the class.
And with only a few public methods and properties, I know exactly which functions and which properties are being called within my application. This keeps things simple because I know that the changes I make to elements of private scope will only have an effect within the class. Therefore if I make a change and there’s a bug, I know where the problem lies.
** You should try this. Look at one of your longer classes and see how many private and public functions and properties you have. See if you can make most of those elements private, and only a few public.
Maintain Separation of Concerns
Separation of concerns is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. For example the business logic of the application is a concern and the user interface is another concern. Changing the user interface should not require changes to business logic and vice versa. — java-design-patterns.com
This principle is tied very closely with Modularity and having a Single Source Of Truth. I mention this specifically though because today, especially in a startup, technologies can change very fast. With a startup, you may today be using a BaaS like Firebase, and then tomorrow you’re building an API from scratch. It happens, I’ve done it before.
When such drastic changes can be made at any moment, you have to be prepared for something like this.
I focus on the separation of concerns for my API requests more than anything else, since I find that the backend is subject to the possibility of the most changes.
I always have 2 layers, one is responsible for making the actual API request. The second layer is responsible for accessing the layer which makes the API requests.
To illustrate, this is what it would look like if I was trying to get a list of users from the server.
Notice the two layers. The first layer that I randomly called APILayer is responsible for making the request and it returns a raw JSON object. The second layer, UsersAPIAccessLayer, makes calls to the APILayer’s executeRequest() function. It then converts the JSON result into an array of User objects. In the UsersView I call the UsersAPIAccessLayer.getUsers method. I never call the APILayer from outside an APIAccessLayer.
Why is this advantageous?
Because if something changes on the backend. Maybe…
- The structure of the JSON object has to change.
- You’re doing a complete rehaul of the backend.
Then when this happens, you only have to change one of two places. Either the APILayer or the APIAccessLayer.
If the JSON object structure has changed, then you only need to update the UsersAPIAccessLayer. (If your User object conforms to the Codable protocol then you only have to change the User object.)
If you’re changing your backend completely, you only need to make changes to the APILayer.
This happened to us recently. We’d been using Firebase for our backend for a while, but it was going to be too expensive moving forward. We began building our own backend. As changes were implemented, we only had to make a few minor, low-risk changes to the code because we had already implemented the separation of concerns principle. We made the changes in a few hours.
Another instance where this happened was a few years back when I had built an iOS app for a company based in Las Vegas. The app was being used heavily and could not afford to go down. They had been using Parse, but we felt it better to migrate over to Firebase. Because of the separation of concerns principle, this took us only a few days. We only had to make changes in one area. We made the changes, and there were zero issues once we pushed.
If you have a network layer that is accessed directly from many different areas in the app, then what happens when you make a change to the network layer? It could potentially mean that you have to make changes all throughout the app, right? The goal is to lower the probability of that happening. That’s why I use the specific architecture that I mentioned above. It reduces technical debt.
These are principles that will make you a great developer. As I mentioned before, development is an art form, so the way that you write your code will be unique to you. I’ve shown you principles, not rules that you should try to apply when you code. Remember, programming is more of an art. There are many different ways to do the same thing. You must adapt the principles to your coding style.
Of course, this will take time. It takes time to make these principles your own. But the time is well worth it. Following these principles will decrease the amount of technical debt you write and other developers will appreciate working on your codebase because you’ll be writing ‘beautiful’ code.
If you enjoyed this article, then clap for it. Some people don’t know you can clap up to 50 times for the same article. Try it!