CS 530 - Advanced Software Engineering

Reliable Programming

Reference: Sommerville, Engineering Software Products, Chapter 8

 

Software quality

Creating a successful software product does not simply mean providing useful features for users. You need to create a high-quality product that people want to use. Customers have to be confident that your product will not crash or lose information, and users have to be able to learn to use the software quickly and without mistakes. There are three simple techniques for reliability improvement that can be applied in any software company.

Fault avoidance

Complexity is related to the number of relationships between elements in a program and the type and nature of these relationships The number of relationships between entities is called the coupling. The higher the coupling, the more complex the system. A static relationship is one that is stable and does not depend on program execution. Whether or not one component is part of another component is a static relationship. Dynamic relationships, which change over time, are more complex than static relationships. An example of a dynamic relationship is the 'calls' relationship between functions.

Types of complexity and how to reduce it

Definition of a design pattern: A general reusable solution to a commonly-occurring problem within a given context in software design. Design patterns are object-oriented and describe solutions in terms of objects and classes. They are not off-the-shelf solutions that can be directly expressed as code in an object-oriented language. They describe the structure of a problem solution but have to be adapted to suit your application and the programming language that you are using. Two fundamental programming principles are the basis for most design patterns:

Common types of design patterns

Refactoring means changing a program to reduce its complexity without changing the external behavior of that program. Refactoring makes a program more readable (so reducing the 'reading complexity') and more understandable. It also makes it easier to change, which means that you reduce the chances of making mistakes when you introduce new features. The reality of programming is that as you make changes and additions to existing code, you inevitably increase its complexity. The code becomes harder to understand and change. The abstractions and operations that you started with become more and more complex because you modify them in ways that you did not originally anticipate. Martin Fowler, a refactoring pioneer, suggests that the starting point for refactoring should be to identify code 'smells'. Code smells are indicators in the code that there might be a deeper problem. For example, very large classes may indicate that the class is trying to do too much. This probably means that its structural complexity is high.

Input validation

Input validation involves checking that a user's input is in the correct format and that its value is within the range defined by input rules. Input validation is critical for security and reliability. As well as inputs from attackers that are deliberately invalid, input validation catches accidentally invalid inputs that could crash your program or pollute your database. User input errors are the most common cause of database pollution. You should define rules for every type of input field and you should include code that applies these rules to check the field's validity. If it does not conform to the rules, the input should be rejected.

Methods of implementing input validation

Failure management

Software is so complex that, irrespective of how much effort you put into fault avoidance, you will make mistakes. You will introduce faults into your program that will sometimes cause it to fail. Program failures may also be a consequence of the failure of an external service or component that your software depends on. Whatever the cause, you have to plan for failure and make provisions in your software for that failure to be as graceful as possible.

Failure categories

Failure effect minimization principles:

Exceptions are events that disrupt the normal flow of processing in a program. When an exception occurs, control is automatically transferred to exception management code. Most modern programming languages include a mechanism for exception handling. In Python, you use **try-except** keywords to indicate exception handling code; in Java, the equivalent keywords are **try-catch.**

Activity logging is a technique where you keep a log of what the user has done and provide a way to replay that against their data. You don't need to keep a complete session record, simply a list of actions since the last time the data was saved to persistent store. Similarly, auto-save is a technique where you automatically save the user's data at set intervals - say every 5 minutes. This means that, in the event of a failure, you can restore the saved data with the loss of only a small amount of work. Usually, you don't have to save all of the data but simply save the changes that have been made since the last explicit save.

If your software uses external services, you have no control over these services and the only information that you have on service failure is whatever is provided in the service's API. As services may be written in different programming languages, these errors can't be returned as exception types but are usually returned as a numeric code. When you are calling an external service, you should always check that the return code of the called service indicates that it has operated successfully. You should, also, if possible, check the validity of the result of the service call as you cannot be certain that the external service has carried out its computation correctly.

Useful links