Common cases where the requirements gathering process impacted system results
All the information discussed up to this point in the chapter is useful if you want to design software following the principles of good engineering. This discussion is not related to developing by using traditional or agile methods, but focuses on building software professionally or as an amateur.
It's also a good idea to know about some cases where failing to perform the activities you read about caused some trouble for the software project. The following cases intend to describe what could go wrong, and how the preceding techniques could have helped the development team to solve the problems.
In most cases, simple action could have guaranteed better communication between the team and the customer, and this easy communication flow would have transformed a big problem into a real solution. Let's examine three common cases where the requirements gathering impacted the results of performance, functionality, and usability.
Case 1 – my website is too slow to open that page!
Performance is one of the biggest problems that you as a software architect will deal with during your career. The reason why this aspect of any software is so problematic is that we do not have infinite computational resources to solve problems. Besides, the cost of computation is still high, especially if you are talking about software with a high number of simultaneous users.
You cannot solve performance problems by writing requirements. However, you will not end up in trouble if you write them correctly. The idea here is that requirements must present the desired performance of a system. A simple sentence, describing this, can help the entire team that works on the project:
Non-functional requirement: Performance – any web page of this software shall respond in at least 2 seconds, even when 1,000 users are accessing it concurrently.
The preceding sentence just makes everybody (users, testers, developers, architects, managers, and so on) sure that any web page has a target to achieve. This is a good start, but it's not enough. A great environment to both develop and deploy your application is also important. This is where .NET 5 can help you a lot; especially if you are talking about web apps, ASP.NET Core is considered one of the fastest options to deliver solutions today.
If you talk about performance, you, as a software architect, should consider the use of the techniques listed in the following sections together with specific tests to guarantee this non-functional requirement. It's also important to mention that ASP.NET Core will help you to use them easily, together with some Platform as a Service (PaaS) solutions delivered by Microsoft Azure.
Understanding caching
Caching is a great technique to avoid queries that can consume time and, in general, give the same result. For instance, if you are fetching the available car models in a database, the number of cars in the database can increase, but they will not change. Once you have an application that constantly accesses car models, a good practice is to cache that information.
It is important to understand that a cache is stored in the backend and that cache is shared by the whole application (in-memory caching). A point to focus on is that when you are working on a scalable solution, you can configure a distributed cache to solve it using the Azure platform. In fact, ASP.NET provides both, so you can decide on the one that bests fits your needs. Chapter 2, Non-Functional Requirements, covers scalability aspects in the Azure platform.
Applying asynchronous programming
When you develop ASP.NET applications, you need to keep in mind that your app needs to be designed for simultaneous access by many users. Asynchronous programming lets you do this simply, giving you the keywords async
and await
.
The basic concept behind these keywords is that async
enables any method to run asynchronously. On the other hand, await
lets you synchronize the call of an asynchronous method without blocking the thread that is calling it. This easy-to-develop pattern will make your application run without performance bottlenecks and better responsiveness. This book will cover more about this subject in Chapter 2, Non-Functional Requirements.
Dealing with object allocation
One very good tip to avoid a lack of performance is to understand how the Garbage Collector (GC) works. The GC is the engine that will free memory automatically when you finish using it. There are some very important aspects of this topic, due to the complexity that the GC has.
Some types of objects are not collected by the GC if you do not dispose of them. The list includes any object that interacts with I/O, such as files and streaming. If you do not correctly use the C# syntax to create and destroy this kind of object, you will have memory leaks, which will deteriorate your application performance.
The incorrect way of working with I/O objects is:
System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\sample.txt");
file.WriteLine("Just writing a simple line");
The correct way of working with I/O objects is:
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\sample.txt"))
{
file.WriteLine("Just writing a simple line");
}
It might be worth noting that this correct approach also ensures the file gets written (it calls Flush). In the incorrect example, the contents might not even be written to the file. Even though the preceding practice is mandatory for I/O objects, it is totally recommended that you keep doing this in all disposable objects. Indeed, using code analyzers in your solutions with warnings as errors will prevent you from accidentally making these mistakes! This will help the GC and will keep your application running with the right amount of memory. Depending on the type of object, mistakes here can snowball, and you could end up with other bad things at scale, for instance, port/connection exhaustion.
Another important aspect that you need to know about is that the time spent by the GC to collect objects will interfere with the performance of your app. Because of this, avoid allocating large objects; otherwise, it can cause you trouble waiting for the GC to finish its task.
Getting better database access
One of the most common performance Achilles' heels is database access. The reason why this is still a big problem is the lack of attention while writing queries or lambda expressions to get information from the database. This book will cover Entity Framework Core in Chapter 8, Interacting with Data in C# – Entity Framework Core, but it is important to know what to choose and the correct data information to read from a database. Filtering columns and lines is imperative for an application that wants to deliver on performance.
The good thing is that best practices related to caching, asynchronous programming, and object allocation fit completely into the environment of databases. It's only a matter of choosing the correct pattern to get better-performance software.
Case 2 – the user's needs are not properly implemented
The more that technology is used in a wide variety of areas, the more difficult it is to deliver exactly what the user needs. Maybe this sentence sounds weird to you, but you must understand that developers, in general, study how to develop software, but they rarely study to deliver the needs of a specific area. Of course, it is not easy to learn how to develop software, but it is even more difficult to understand a specific need in a specific area. Software development nowadays delivers software to all possible types of industries. The question here is how can a developer, whether a software architect or not, evolve enough to deliver software in the area they are responsible for?
Gathering software requirements will help you in this tough task; writing them will make you understand and organize the architecture of the system. There are several ways to minimize the risks of implementing something different from what the user really needs:
- Prototyping the interface to achieve an understanding of the user interface faster
- Designing the data flow to detect gaps between the system and the user operation
- Frequent meetings to be updated on the user's current needs and aligned to incremental deliveries
Again, as a software architect, you will have to define how the software will be implemented. Most of the time, you are not going to be the one who programs it, but you will always be the one responsible for this. For this reason, some techniques can be useful to avoid the wrong implementation:
- Requirements are reviewed with the developers to guarantee that they understand what they need to develop.
- Code inspection to validate a predefined code standard. We will cover this in Chapter 19, Using Tools to Write Better Code.
- Meetings to eliminate impediments.
Remember, the implementation matching the user needs is your responsibility. Use every tool you can to meet it.
Case 3 – the usability of the system does not meet user needs
Usability is a key point for the success of a software project. The way the software is presented and how it solves a problem can help the user to decide whether they want to use it or not. As a software architect, you must keep in mind that delivering software with good usability is mandatory nowadays.
There are basic concepts of usability that this book does not intend to cover, but a good way to meet the correct user needs when it comes to usability is by understanding who is going to use the software. Design Thinking can help you a lot with that, as was discussed earlier in this chapter.
Understanding the user will help you to decide whether the software is going to run on a web page, or a cell phone, or even in the background. This understanding is very important to a software architect because the elements of a system will be better presented if you correctly map who will use them.
On the other hand, if you do not care about that, you will just deliver software that works. This can be good for a short time, but it will not exactly meet the real needs that made a person ask you to architect the software. You must keep in mind the options and understand that good software is designed to run on many platforms and devices.
You will be happy to know that .NET 5 is an incredible cross-platform option for that. So, you can develop solutions to run your apps in Linux, Windows, Android, and iOS. You can run your applications on big screens, tablets, cell phones, and even drones! You can embed apps on boards for automation or in HoloLens for mixed reality. Software architects must be open-minded to design exactly what their users need.