SoftwarePuzzler
 

Fundamental Laws of Software Development

Introduction

The basic assumption made in the Software Puzzler project is the existence of a set of fundamental laws that are always true in all software development. If these laws can be found and incorporated in the software development process, the engineer will benefit.

This is the opposite approach to many existing software development processes. The standard approach is that the author tries to shape reality after the current fads in software engineering, instead of incorporating reality into the process. The end result is a brittle process that must have its ideological foundation vigorously defended to not be abandoned. The actual development work taking place is still governed by the fundamental laws of software engineering, and with some luck some of them might be explicit in the process, but mostly the process tries to work against them or just ignores them.

The quest for the laws will never end. This is a continuous effort that constantly can be improved upon. It is important that we all agree on the validity of the laws, or they are not as fundamental as intially believed.

Software Construction

  • The ultimate goal of software development is to gain access to universal computability, extending the limited capacity and configurability of the human brain.

The ability to tap some of the available computability resources in the universe, and use it to your own liking is very valuable. This was long ago realized by evolution, which created plastic brains for this purpose. The human brain is currently the one to be known to be the most evolved and it can be used to think about computability itself.

The computer was invented for this purpose as well, to make some extra computability available for arbitrary use. The customer investing in computer systems knows this. A software engineer must also take advantage of this available computability and not be limited by his own brain. This has an immediate connection to the craftsmanship of software engineering and the awareness of what you are actually doing for your customer.

Be clever, construct code generators, automate testing, separate information and abstractions, make sure you always keep information in executable form, reuse abstractions and information, excel in the use of automation.

Avoid cut'n'paste programming and endless repetition of the same code structures over and over again. Don't store valuable information where it can't be accessed by the system you are building. Don't manually repeat a task over and over again if it can be automated. Avoid manual work as often as possible.

Think about what you are doing. All the time.

  • Programming is the construction and implementation of new languages using other languages.

The notion of languages built upon languages goes all the way from the initial taming of universal computability at the transistor level, through the programming languages and the business rules, and ends at the human-computer interface. The strength of generic computation is its ability to support new opportunities for computation at abstraction levels better suited for the problem at hand. All computation is still the same, nevertheless.

The programmer is free to invent new notations when necessary. The programmer takes on a great resposibility when inventing new notations, as the notation must be executable in some way. Note that it is not the notation in itself that determines its properties, but this is determined by the component that interprets or compiles the notation.

This law is the foundation on which LISA rests. When the law is expanded into a domain language for architectural descriptions, the engineer gets the ability to analyze and describe existing and future system architectures.

The use of language above can be substituted with abstraction, and mean the same thing in certain contexts. The use of language is preferred, as it means that the notation is complete for its purpouse, without references to external terminology.

When the number of languages that are defined and implemented in the development of a system increases, more natural opportunities for documentation are created, reusability increases, development is easier to coordinate and overall risk is reduced.

  • All programming is the same.

No matter if you are formalizing requirements, write a C program, draw statecharts in a CASE tool, constructing a Makefile, design a web page or using the latest in programming language theory, you are performing the same mental activity. You are programming.

Sometimes, the programming activity is disguised and called things like design, modeling, analysis or executable UML, but don't be fooled by this misuse of language. They are all different paradigms of programming, suitable for different problem domains. If you use any of them, just make sure the result of each activity is executable, as it should be in all programming activities.

Each of the different forms of programming has its place, and none of them can be claimed to be the ultimate solution to all programming problems. Too much time is spent on looking for and promote the killer abstraction, the paradigm that will render all other useless. Instead, time should be spent on finding the best language for a specific domain.

  • A software system always has an architecture.

Even if you don't have an architect in the project organization, your system has an architecture. It is the collection of all decisions regarding operating system, programming language, third party libraries, file formats, communication protocols, component breakup, and so on. The architect can be small and simple or large and complex. The architecture can be completely known or mostly unknown lurking in the code. The best thing an engineer can do regarding the architecture is to know what it looks like and control its future development.

Requirements

  • Requirements specified by a non-programmer are always informal and must be formalized before they can be used in the construction of software systems.

The requirements put forth by the customer can seldom be executed by a computer. The wish that this could be true has been the goal of many reseach projects, but few useful results has come out of it. The task of formalizing requirements is a programming task, equal to the implementation of a hardware driver within the operating system. The only thing that differs is the domain knowledge.

The closest you can get to an executable requirements specification is to construct a language that is ideal for the domain, let the customer write the program in this language and call this program the requirements specification. This is a very good idea in theory, but falls on the fact that the customer is no programmer and probably wonders why he hired you if he is going to write the program himself. Settle for the next best thing, and write this program yourself, with the help of the customer. He hired you to help him capture his domain knowledge in a formal specification. The rest is up to you.

If the fact that requirements need to be formalized is ignored doesn't make it go away. Instead, the formalization is spread over all activities in the system development, and it becomes very difficult to track the implementation of each requirement. The evolution of system components becomes highly coupled with the evolution of the requirements, which makes the system hard to maintain.

Formal proofs intended to show that the program implementation is correct can be used if the formal requirements are accepted as axioms. No automated process can detect if the formal requirements are correct in the first place.