Save yourself time and effort.
Anyone can learn to write code. Once you start getting into writing programs with a decent level of size and complexity there are a few things you can do to make your code easier to read, easier to maintain and more robust. Some of them will take a small amount of extra time and effort to implement as you go but will pay off greatly in the long run as your code will be more reusable, easier to test, less prone to errors in the first place and easier to modify/ extend as your needs change (which they pretty much always will).
The code examples on this page are in a fictional language called RyanScript. I have done this deliberately as there are many programming languages out there, each different in their syntax. The concepts discussed on this page are generic and apply no matter which language you are developing your software in.
The readability of your code is vitally important. Increasingly so as your programs increase in size. The following basic practices will improve the readability of your code.
Naming things in a descriptive way makes it much easier to understand what is going on. Avoid the temptation to try and look like a 1337 Hax0r by only using single letters or cryptic names. It doesn't make you look cool, it just makes your code harder to read, debug, maintain etc and makes you look immature and childish.
It's also a good idea to have slighly different formatting conventions for different types of things so that when you see names within your code it is immediately obvious what type of thing it is (variable, constant, function, class etc). Here is a format I like to use but as long as you are consistent you can use whatever format works for you.
Functions should be named using verb_noun format. State the action, what is being done, followed by what it is being done to. eg. get_prices or mark_responses
Typical verbs include:
But you will probably have others as well specific to what your program is doing.
Ensure that you are consistent with your naming scheme and style.
A boolean is a value which can be either True or False. They are quite useful and are used all the time in programming. A variable can be boolean. eg.
assignmentSubmitted = True;
A function can also return a boolean value. eg.
assignmentSubmitted = has_assignment_been_submitted(studentID);
In general you should try to name your booleans (variables and functions) positively. It makes it harder to misunderstand their meaning and get it wrong.
Good names:
Bad names:
Naming your booleans positively avoids code like the following which is easy to misread:
! means not
Now that you've got your naming down, the next thing to look at is formatting and indenting. This is essentially the spacing between elements of your code and has a big impact on readability. Elegantly formatted code makes it easier to see the structure of your logic and also makes it easier to spot and correct syntax errors. Here is an example of some poorly formatted code:
There is actually a syntax error here. Let's format the code better and the syntax error should become obvious.
Now the structure of the code is clearer and the syntax error is easier to see. (It is a missing closing curly bracket) You'll also notice that newlines have been added to separate out logical elements of the code. This again helps to illustrate the structure of your program. There are various ways you can format your code. All are perfectly valid as long as they make your code easy to read and also that you are consistent. Here is another popular way of formatting (with the missing curly bracket added in):
Formatting and indenting your code may seem like a tedius waste of time but once you get the hang of it it becomes habbit and you do it without consciously having to think about it. The amount of extra time it takes is also a fraction of the time you will save trying to find syntax and logic errors.
When undertaking software design and development, one of the things you soon come to realise is that you really can't predict how your project is going to evolve and grow. No matter how much you plan and think you know what you want, the reality is that you will have new (and hopefully better) ideas as the project comes together. Once you start using your product your views on things will also change. You will also identify bugs which need to be fixed.
Designing for maintainability means organising your code to make it easy to fix and improve.
Some of the ideas discussed in this section also apply to the next section (Extendability).
If you've organised the structure of your program well then you should end up with higher level functions which direct and manage lower level functions which do the work. In particular you should have a main function which coordinates in a broad, easy to understand way. Here is an example for playing a game:
Looking at this function it is easy to see how the game code is structured. It also helps to lead to a logical breakdown of the code which will make developing easier to manage.
There are a few ways you can order your functions and classes but there should be some logical order to them in your source files. You may have a series of helper functions. It often makes sense to group these together at the bottom of your file. You should then try and order the rest of your functions in a roughly chronological order. eg. if you have a main function which is called first then this should be the first function at the top of the page. If it calls a function to displya a Start screen then that function should be next, etc.
Doing this makes it easier to read through and follow your code. When trying to follow through and understand some processing you shouldn't need to constantly be scrolling up and down in the file trying to find the next function which is referred to.
You won't be able to get a perfect chronological order as no doubt there will be functions called from multiple places but you should try and achieve this as much as possible.
Nesting is where you have a decision or a loop within another decision or loop. eg.:
In this code the if statement is nested within the while loop. The if statement could contain another loop or decision within it, which could also have another loop or decision within it and so on.
Deep nesting is where your code nests down to more than a few levels. The result is code which is hard to read and easy to make mistakes with. The logic alse ends up being messy and makes maintainability more difficult. If you end up with code which has deep nesting your really want to take a look at it and see if it can be rewritten in a cleaner way (nearly all the time it can be). Here is a simple example:
This could be better rewritten as:
Breaking your processing down into smaller, more targeted functions can also help in this regard.
When designing and developing software it is easy to get excited by what can be done. You're making progress, things are coming together, I could just add this, and just add that. Your code is structured well so doing so is easy. Before you know it your elegant and beautiful project is messy and cluttered. That intuitive interface has become a mess of buttons and data, your code is bloated and slowing down. This is an easy situation to get yourself into.
I'm not saying don't add new features but make sure it is done sparingly and that they are planned and integrated cleanly. More importantly, make sure you don't add new features unless they will really add value. Spending time improving existing code (making it run more efficiently and robustly) is generally way more valuable than just throwing new features in.
Writing good comments is a balancing act and an art. Comments should explain what the code achieves, not what it does. If you have done the things mentioned above in this section and the previous section then you will have something called intrinsic documentation. That is, the code itself makes it obvious what it is that it does. Comments are used sparingly to fill in the gaps. Comments in your code should be included:
At the top of your file is it common to include some comments with details of the file. These details can include the date it was written / last updated, the author and a brief outline of what the code is that is within the file. For example:
Preceeding each of your functions it is also advisable to include a comment with details of the function. Useful details are purpose of the function, what parameters it accepts and what it's return value is. This makes maintaining and extending your code easier as you can quickly skim these comments to see if there is existing code that does what you want that you can call rather than having to write.
It is also a good idea to use comments to separate out logical sections of your code. You should have done this already for small sections of your code with newlines but higher levels of groupings should be commented. You may for instance have a collection of functions which are related for formatting of data prior to rendering on the screen. You would choose to group them all together under a comment like such:
You may be developing a game and have a relatively large section of code related to working out the movements of enemy characters. That section may be identified with a comment like such:
Sections of code, or particular instructions which serve a purpose which is not immediately obvious, or things which are done a particular way for a specific reason, should also be commented.
Comments like the following should be avoided. They are redundant and only serve to reduce the readability of your code.
If the development of your software is successful you should have a good product. Over time you will want to add new features to it. You want to ensure that adding these feautes is as easy as possible. You also want to ensure that adding new features doesn't inadvertantly break existing functionality. Designing and developing your software with the following ideas in mind will make extending your product a less stressful experience.
Global variables are available everywhere within your code. It can be tempting to use global variables so you don't need to think about passing the data around as parameters. Doing so might seem convenient in the short term but over time leads to a higher chance of changing variables when you don't intend to.
There are some insances where globals are valid to use and useful. If a variable is set once at the beginning of execution of the software and then doesn't change, and is required frequently, then it is a good candidate for being set as a global. An example of this might be a variable which holds the path to the directory which holds configuration files for the software.
It is also good to name globals in a way which makes them stand out, eg all uppercase or with a certain prefix.
This is an idea which originated in the area of databases and the management of data. It is just as applicable to developing code however. DRY (Don't Repeat Yourself) If you find yourself writing code that is very similar to previous sections of code (or even worse copying and pasting code from ealier on) then you have probably found a good candidate for creating a generic function that can be repeatedly called instead.
Doing so will reduce the amount of code you have to write and also reduce the possibilities of making mistakes.
There is a strong chance that you will want to perform similar actions in various sections of your code. If you move that functionality into a function and write it in a generic way then you can just call that function instead of having to write a series of similar but slightly different functions. This has the benefit that you only have to test one section of code rather than several. If you need to change the way the action is performed you only need to change one section rather than several. Extending your software (where there is a high chance you will be doing things you didn't originally anticipate) should also be easier if the functions are generic in nature.
As an example of this, lets say we want to check the existence of a user. We could write a function which accepts a single argument which is a userID which returns True if a user with that userID exists in the database. But then when a new user signs up we may wish to check if an existing user has already used that email address. When logging into the system we may wish to check the existence of a user by their userName. We could write a slightly different function for each of these scenarios.
Or we could write one function which will accept a userID, emailAddress or userName.
For something like this you may consider also including a second parameter which states what type the token is. Although it would be very easy to work it out (eg, if the value is all numbers it must be a userID, if it contains an @ symbol it must be an email address, otherwise it is a username there may be the possibility for users to do malicious things. You should sanitise all data on forms before using it but it's always safer to not assume things.
Another good candidate is sorting. If you ever write a function which returns a sorted array of items, make sure it has a parameter which allows you to return the array sorted either ascending or descending. You never know when later on you may want the same list but in the opposite order.
It can be tempting to combine obtaining data, formatting it and printing (or rendering) it in one function. Doing so, however, makes your code less generic and less reusable. Instead you should have separate functions for processing and rendering.
Let's say you are developing a game and have a function which reads the previous scores from a file, works out the top 10 scores and prints them to the screen. It's very efficient and works fine but then you decide to extend the game and during play you want to print an encouraging message if the users score gets close to being in the Top 10. You can't just call the current function as it will just print the data. We will have to write another function that contains much of the functionality of the first one. If we decide later on to change the game to showing Top 20 instead of Top 10 we have to remember to udpate both of them.
If instead we have two functions, one which returns an array of the Top scores and one which accepts that array and prints the data then all we would have to do is call the first function and we have our data ready to go.
So you're doing all the ideas listed above and your programming is looking pretty good. To take things to the next level you need to start thinking about the bigger picture structure of your programs. Here are a few ideas to get you started. I've only briefly introduced each of the ideas below. If one of them appeals to you you should do further research on them. There is a lot of great information about all of them easily available through a search in your favourite search engine. Each of them is quite involved but quite effective and powerful when implemented properly. Studying them is a great way to improve your software design and development ability.
If you want to bake a chocolate cake you don't sit there and think about how you might put together what ingredients to achieve it. Instead you grab a recipe and follow that. The recipe was put together by people with a lot more experience than you (most likely) and you are confident it works as it will have been tested and refined and proven over time. Design patterns are similar but for code. A lot of the problems you want to solve to develop your software will have been encountered by many other developers many times before. They have developed tested and proven ways of structuring code in order to produce efficient and robust code. Rather than reinvent the wheel you can copy and learn from their experience.
Most design patterns are described in a generic, language agnostic way (that is, they can be implemented in any language) though there are some which are language specific and utilise specific features of that language.
Here are three general categories that design patterns tend to fall into:
Understanding and utilising the ideas presented in these patterns will help to improve your coding technique. It will also provide valuable approaches to making your code more generic and reusable.
REST stands for REpresentational State Transfer. It is a design methodology that looks at splitting your software into two logical parts. The first part manages data and access to that data. The second part manages processing of that data. It is very popular in areas such as website development where you would typically have data stored on a server (within a database) and that data is accessed and processed by many clients which could be via a website or mobile app or desktop program etc. The idea is to separate the two sides in such a way that they are independent. Neither side needs to know anything about the other side in terms of what they do or how they do it. They just need to know that there is an API (application programming interface). Think of an API as a means to ask the other side for what you want.
There is a reasonable chance that the software you are developing saves and reads data from another source in some way (eg, database, file, requests to another system). If so then your program will benefit greatly from a solid layer for managing this activity. If this aspect of your program is done reliably, efficiently and with an intuitive interface then the rest of your code becomes much easier to write and manage.
CRUD stands for Create, Read, Update, Delete. These are the four basic actions that you will perform on data and your module for interfacing with the storage of your data should provide an easy and reliable way to do each of these actions for each main bit of data for your system.
A lot of people like to develop games. Games are also quite interesting in the way they work and require a bit of thought put into how you structure your software in order for it to work effectively. Unlike many programs which are sequential in their processing, games have an intricate interweaving of elements (player, enemies, environment etc) which are constantly interacting with and affecting each other. Here is a simple model which can help you to implement this in an easy to manage way. It is called the Game Loop.
Think of the Game Loop as a simple design pattern. It breaks your software down into 3 distinct stages.
And loop continuously until the game ends.
At the end of each cycle of the loop, wait a tiny amount of time before starting again. This tiny amount of waiting is so that you know exactly how fast the loop runs and can adjust elements at the right amount to get the speed you want (otherwise things would move faster or slower depending on how fast the CPU on your device was). A typical way to set this up is to match it to the frame rate of your monitor (eg. 60 times per second).
Here is an example of what your initial section of code might look like:
For more complex games the game loop may not be tied to the aliveness of the player but just until the player quits. This way the game loop can handle the displaying of various game screens such as initial welcome screen, settings page, actual game, game over screen etc.
The above template is intended as a starting point. You will no doubt need to build upon it to suit the characterisics of your game. It should maintain this general overall structure however.
People often think that writing your code is the last step of designing and developing your software. As long as it works it doesn't matter too much if it's done poorly. Testing and bug fixing your code to actually get it working can easily take up a lot more time than you actually spend writing the code if your writing is done poorly. If your software is structured logically working off a good plan from your design documentation then writing the code will be easier, the number of bugs you have to fix will be less and fixing those bugs will be less stressful. Keeping the ideas discussed on this page in mind when both writing your code and also designing the overall structure of your software will make for a much more enjoyable experience with a better quality final product as a result.