How to develop software — a basic guideline
Updated: Dec 29, 2022
Software development is an art. And, as art, there's no unique definition of what's right and wrong.
Of course, years of research (and software development) provide us with good practices, designs, and techniques to follow. They help us to avoid mistakes other people have been through. They give us a paved path to walk instead of building it ourselves. But, as art, we can not use all the techniques all the time.
This document is only a guideline about how to develop good software.
The Software Development Life Cycle
The process of developing software (or an application or a feature) has a set of phases: we decide what we want to do, we implement it, we test it, and we deliver it.
How we do each step and what each encompasses depends on the software/application we are developing, our expertise, and our goals.
It is tempting to think we don't need to go through these steps. But, it is practically impossible to develop software without deciding what we want to do. No one ever starts coding and suddenly gets an application out of it.
Any software would have at least two phases: deciding what should be done and doing it.
However, if we want to deliver quality software, a good definition of the software life cycle helps us to deliver it faster and meet (or exceed!) the client's expectations.
Here, we will consider the software cycle in three big phases: planning and requirements, prototyping, developing and testing, and deploying and maintaining.
Planning and requirements
We all know: prototyping and writing code is fun; it’s rewarding to see an idea being born and code working. However, good planning and definition of requirements can make our development easier and more effective.
Before starting an implementation, we should gather the requirements we want to fulfill. This helps us to be able to identify when our feature/functionality is done.
How can one know if a functionality or feature is done if we don’t know our goal? Or, how the Cheshire cat would say to Alice when she asked where to go: "That depends a good deal on where you want to get to”.
Implementing a functionality without planning and defining requirements can become a never-ending story.
Here are some questions we can ask ourselves before starting any development.
It is okay not to have answers for all of them, yet: not knowing is already helpful information for planning.
Planning: where do we want to go?
— what problem are we trying to solve? Why?
— is there a similar software on the market? If so, how will we differentiate from it?
— what results do I expect when my feature is done?
— how this new feature/functionality relates to what is already implemented?
What functionalities do we have envisioned for the short future?
Do we need to update the software architecture?
Is this new feature compatible with previous features?
Who is our user group?
Which platforms are we supporting?
Requirements: what do I need to get there?
— what tools do we need for the development?
Do I need hardware to develop/evaluate my feature?
What behavior do we expect in case of success?
What behavior do we expect in case of failure?
Are there other libraries or tools we need to integrate?
Are there requirements for data?
Do we need to offer real-time performance? What is our definition of real-time?
Prototyping, developing, and testing
Here comes the fun part! Prototyping, writing code, and creating tangible results!
But again, before starting to write code, it is essential to understand how we want to do it.
Prototyping is the definition of an early version of the feature/application. It demonstrates how the software will look and work, it is an important tool to get feedback about where we want to go, and it is cheaper to adapt at this stage.
Once we have a prototype and confirm the software's requirements, we can write the actual software.
Prototyping: what is the minimum I can implement to prove the viability of the software?
— design discussion
sketch of interfaces
— confirmation of requirements
— identification of new features/improvements
— evaluation of integration with other features
After prototyping, the software can be written. The development can be done by a single developer or broken down and written by several people.
Regardless, using a version control system is important. A version control system allows us to track changes in the code and help us with code compatibility when different people are working together.
Developing software is more than just writing code. It also includes testing and writing documentation.
Tests are code, too! And are extremely important when making software available to users; it will help to reduce the number of bugs, leading to user satisfaction and retaining users.
They can ensure that users can use the software and that it won’t break in case of a bad input or unexpected actions. A good set of tests will try to break the software and define its limits.
Testing: to find bugs and guarantee functional software
— each feature should work correctly (unit tests)
— features should work when integrated (integration tests)
find interface problems between functionalities
does the state of the software change the results of a feature?
— the software should attend to user expectations (system or functional tests)
does the feature meet technical and functional requirements?
often tested by someone outside the development cycle
— performance tests
is the feature taking too long to run?
is the feature able to handle higher demands?
is the feature reliable?
Documentation can be formal (with an explicit document) or informal (with comments in the code).
Documentation allows developers to understand why a certain procedure was used and help users to solve their problems or questions.
Documentation: formal or informal explanation about the software
— user documentation
basic explanation of the feature: what does the feature do? how to use it?
troubleshooting guide: what to do in case of issues/bugs/problems?
FAQ: what are the most common mistakes or confusions?
— developer documentation
why was that implementation chosen?
what are the magic numbers?
what are the dependencies of that code/feature?
what are the assumptions regarding parameters/returning values?
Deploying and maintaining
At this point, the development cycle is almost done. The software is implemented and running, and now it is time to put it in the hand of the users. This is the deployment.
Be aware that software release is not the same as software deployment. A release is a specific version of a code and its dependencies made available for deployment.
Deployment can be a simple task like updating a database with the software where users can download it, or it can be complex if there are dependencies related to the software being deployed.
Deploying: making software available to users
— Defining the name, description, and version of the software to be deployed
— Defining the dependencies of the newly deployed version
is the user hardware/system still able to run the new software?
— Testing the deployment
can the user install the software?
does the way to use the system change?
When the software gets to the user's hand, we start the maintenance part of the software development cycle.
Now, users can find bugs that were not discovered during tests, and all these issues need to be resolved; and they can initialize new development cycles.
Maintaining software is fixing bugs and planning additional features for future releases.
Maintenance work is often overseen, which can lead to the degradation of the software’s structure /architecture and poor integration/documentation as the software ages.
Maintaining: guaranteeing software to be lifelong
— fixing bugs
— planning new features
Those phases are a piece of broad information about how to develop software.
They can be applied in a hugely diverse set of methodologies. Depending on the project/feature being developed, some might offer more advantages than others.
The most common issue following the Software Development Life Cycle is a misunderstanding of requirements or the users' needs, which leads to software that doesn't meet expectations.
Too complex tasks (or not well-defined) can again lead to misunderstanding specifications and requirements.
And unfortunately, emergencies happen, and we face situations where the implementation has to be done with urgency, and the whole software cycle is not considered. However, we should pay attention to these points and try to anticipate user needs.
The classic method of software development is called waterfall. In this methodology, any phase only starts when the previous one is fully completed.
It is useful for well-defined projects but has a bottleneck in speed since we must go through all the phases one at a time.
“I'm not quite sure what’s the end goal. This is research, and I don’t know if it will work. I can’t follow these planning steps.”
Agile methodologies are built around flexibility, where changes are welcome. If the project has uncertainty, and directions can change based on feedback, research, and discoveries from you, the team, or clients, then agile methodologies do work.
It produces small and incremental changes from the previous release, allowing the team to identify and address issues before they evolve into bigger problems.
Best Practices of Software Development
Phases and methodologies for software development offer us a framework to start. We also have some best practices that can be applied to some or all the phases in the cycle.
Version control of source code
To avoid losing work, to share work-in-progress easily, and to have access to our code from anywhere.
The acceptance criteria are related to each feature being developed and allow us to check if the software meets the user’s requirements.
Continuous Integration (CI)/ Continuous Delivery/Deployment (CD)
Continuous integration is a way to reduce the challenges when we need to put features together. CI is the practice of merging changes to the code with the main branch as often as possible. This way, we ensure that everyone in the team uses similar libraries, for instance, trying to prevent conflicts and duplicated work and checking that the software is not broken whenever a new commit is integrated.
Continuous delivery vs. continuous deployment
The CD in the pipeline can mean Continuous Delivery, Continuous Deployment, or both.
Continuous delivery is a fundamental extension to CI, where the software is automatically released, i.e., goes to a test/production environment, and can be used to deploy anytime.
With continuous delivery, the decision to deploy the software to the users can be done anytime and as early as possible.
Continuous deployment is an extension of continuous delivery, i.e., the software is automatically released to the users. There’s no need to do the deployment explicitly.
This step removes the concept of “release day”. Anytime a new feature is added, tested, and delivered, it would be deployed to the users.
Working on Technical Debts
Technical debt is the result of prioritizing speedy delivery instead of good code. It encompasses bugs, legacy code, and even missing documentation.
Technical debts are not bad per se. However, the accumulation of technical debts can lead to poor-quality software in the long run.
It is important to get technical debt under control by recognizing its existence and promoting operations to tackle them.
The Definition of Done (DoD)
Having a feature implemented and working might not be enough to say the feature is complete.
The DoD follows the requirements. By knowing where we go, we can know if we got there.
The DoD is a tool to help the development team not pursue a moving target.
It was introduced as one of the most important elements of Agile methodologies: to deliver usable software fast, we need to know what usable software means.
— Code Style
— Code review
— Acceptance criteria
build and automated tests
Common attributes for DoD:
is the code enough, or do we need to write the tests as well?
what kind of tests do we need to write?
do we need to have a code review?
who should do the code review?
do we need documentation?
user and/or developer documentation?
do we need to test everything or just a few selected areas of development?
what kind of tests do we need to run?
what exactly should be automated in the tests?
can we leave any specific bugs?
do we need to validate changes by creating a build and running automated tests?
do we need to run static code analysis?
do we need to deploy this feature to certain environments?
Effective communication across the team is the most important best practice in the software development life cycle.
We can only avoid duplicated work and guarantee software quality by knowing what others are doing and having shared knowledge.