Software Engineering - Drawing inspiration from Nature's bag of tricks
I tried to acquaint the beginner C# programmer with the mysterious concept of delegate
in a previous article of mine. If you are a beginner, make sure you read that first. Also, make sure that you read everything linked in all my articles.
My articles get auto-published on CodeProject (RSS magic) and that article was liked by many people there. There is no such thing as feedback overdose. I was not content with CodeProject feedback and posted the aforementioned article on Reddit too. I got a few comments there too.
This article is a commentary on the comments I received on Reddit. The comments were rudimentary. Nothing special. However, I thought it would be best if I go ahead and dissect certain comments so that the world of knowledge encapsulated in them could be revealed.
When it comes to programming (or learning, in general), I know being content is detrimental. There are people willing to learn, and in the process, willing to force those who can teach to go beyond their comfort zone. There are people who can teach and are willing to go the extra mile to help the community. Contentment would have limited both the groups (learners and teachers) to mediocrity.
LANGUAGE DESIGN CRITICISM AND ALL THAT CAN BE LEARNED FROM IT
Of the comments I got on Reddit, the one that I think is the most significant started off like this:
Delegates is one of those features I’m not sure should’ve been added to C# at all. On one hand they help you write code faster, but they are so error prone I’m not sure they’re worth it at all.
This is an opinion. An opinion shared by many, and most significantly by the Java designers and many Java developers. There is nothing wrong with this opinion. The only thing is that it differs from the opinion of C# designers and many C# developers.
If you are a beginner, you may get lost in the syntax jungle or drown in the sea of jargon even before you think of your first solo voyage. Software Engineering can only make sense if one builds mental models. The aforementioned comment is significant because it lets me explain how important it is to understand Software Engineering by relating it to the world around us.
ORIENT YOUR BRAIN TO OBJECT-ORIENTED PROGRAMMING (OOP)
EXAMPLE 1
Who do you think invented the first camera? No, it was not a human. For the lack of a better term and more importantly for the lack of knowledge we conceal that particular detail in the term “Mother Nature”. This allows us to investigate how “Mother Nature” works while not bothering about why she works the way she does. What happens if we do not do this?
Well, let us conjure up a situation. Let us say, you want to apply some lubricant to your bike’s chain. The job is very simple. The algorithm is:
- Find the type of chain you have on your bike
- Find the best lubricant for that type of chain (most suitable compound)
- Purchase the lubricant and apply it on the chain
You do not go about understanding how the lubricant is manufactured, where the materials were sourced from, the quantum mechanical properties of the atoms involved in the lubricant, et cetera. You will end up spending the rest of your life finding the lubricant (reminds me of Buridan’s donkey) while your bike gets eaten by the elements. This is where abstraction helps us. It gives us a good enough starting point. This technique is employed in all fields of human endeavor and is quite popular too. It can be found in Mathematics, in Computer Science et cetera. In OOP, we use abstraction to hide away details. You never need to know the implementation details of, say, delegates. All you need to know is how to use the concept to achieve your goal.
Now, let us get back to our original example. We do not know the true nature of the designer of the first camera. Also, I do not know what the first version of the camera on earth looked like, but the first camera design after forking and a few versions looks like the figure on the left. The original eye design was forked to create human eye. The development continued and what we see today is the current state of evolution. There is more detail to the development/evolution. The existence of various types of human eyes different from each other in various ways (eye color, for example) is clear evidence of a complex development process. This means various development/evolution branches of human eye design exist and are under continuous development. The design is bound to change in the future. New features may be added. Some may be removed. The changes may be governed by variables completely out of our control. Probably, these variables are completely out of our genes’ control too. Nevertheless, changes will occur. Our eyes have a well-defined set of features that work in the environment we inhabit. This set of features is mutable. Changes in environment will force changes in the design of our eyes.
The original camera or eye was again forked to create a fly’s eye. This design too has been under development for a very long time now. However, it is entirely different in design and has a very different set of features. However, it too is a slave of the environment and has to undergo continuous change to remain relevant. That is how it has been, that is how it is and that is how it will always be. It is the environment that forces changes in the design. The designer is forced to adapt according to the environment’s dictate or perish.
Can you see a connection between the real world and Software Engineering? The environment we live in and the market that we expose our software to are exactly as harsh as the other. They follow the same principles, most important of which is
Survival of the fittest
As you can see from the above pictures that even nature could not settle on one design and has been experimenting. The original eye design was further forked to create black-and-white eyes, eyes that can view in the dark, eyes that can see underwater so on and so forth. Thus, there is no one design that could termed as the best. It all depends on what the use case is.
WHAT TO LEARN NEXT?
- Software Development Lifecycle
- Software Configuration Management
- Build and Release Management
- Branching Schemes source control tools
- What is abstraction? (in and outside the domain of OOP)
EXAMPLE 2
Another very interesting subject is that of DNA Replication. Cell replication involves DNA replication.
DNA is made up of two strands and each strand of the original DNA molecule serves as a template for the production of the complementary strand, a process referred to as semiconservative replication. Cellular proofreading and error-checking mechanisms ensure near perfect fidelity for DNA replication.
Does this not sound like parsing, syntax checking, lexical analysis et cetera done during the compilation of a programming language?
Although the algorithm is very complex, the output is very reliable. The DNA syntax and semantics can be considered immutable during the normal DNA replication process.
The rate of semiconservative DNA replication in a living cell was first measured as the rate of phage T4 DNA strand elongation in phage-infected E. coli.[4] During the period of exponential DNA increase at 37°C, the rate of strand elongation was 749 nucleotides per second. The mutation rate per base pair per round of replication during phage T4 DNA synthesis is 2.4 x 10−8.[5] Thus semiconservative DNA replication is both rapid and accurate.
However, this reliability in output and the immutability of DNA syntax and semantics is very much dependent on certain factors. For example, Gamma radiation is very well known to mutate DNA. There are other biological agents too that are capable of changing the semantics of a DNA.
Meiosis is another form of cell division. This is a very special cell division process. The input in this case is paternal and maternal DNAs. The new cell formed has a new DNA inherited from both parents. In this case, however, syntax of the DNA is the thing that is of primary concern. Semantics are not that important.
If the child DNA is syntactically correct, it is allowed to exist. There are mechanisms in cells that test the syntax. It is only when you look at the output that you can determine whether everything was proper.
The picture on the left is an example of meiosis happening because the resulting syntax was proper, but something went wrong semantically. What went wrong could only be determined after the birth of the specimen.
Not all such semantic deviations from normal are bad. In certain situations these prove to be a boon for the species. Just take a look at the Grizzly bear and Polar bear ancestry or dog and wolf relation. These bears are genetically so similar that they can mate. Yet, they are very different. Same is the case with dogs and wolves.
This scenario is very similar to runtime errors in computer programs. The program compiles fine because the program is syntactically correct. However, the compiler is not well-equipped to check the semantics.
Logical errors in the program slip through and can only be caught during testing or worse in the field. Let me show you a code sample that illustrates my point:
class Program
{
static void Main(string[] args)
{
int input = 0;
int.TryParse(Console.ReadLine(), out input);
int answer = 15 / input;
Console.WriteLine(answer.ToString());
Console.ReadLine();
}
}
Yes, I know there are various ways to improve the code above so that it works as expected, but that is exactly what want to say. The code compiles although it has huge gaps. It is only when you run it that you become aware of how wide and deep the gaps are 🙂
The following happens when you run the code and provide zero as input:
WHAT TO LEARN NEXT?
- Various types of Programming languages and their compilation processes
- Compilers, interpreters
- Statically typed vs dynamically typed languages
- Parsers, lexers, regular expression engines
- Compile-time errors vs runtime errors
EXAMPLE 3
This time from the man-made universe.
This is a V-6 IC engine.
This is an inline 4 cylinder.
Now, answer the following:
- Which one is the better?
- Looking at the engines, can you guess which engine was designed by a smarter engineer?
- Which of these engines would a smart user choose?
If you try to answer these questions, you will start understanding that there are many design choices available. This is a phenomenon that can be seen in all fields of human endeavor. Weigh the pros and cons and decide which suits the job at hand. And improvise. Some more thought and you will start seeing the common theme in all these examples. “Mother Nature” and humans think alike. Or more correctly, we humans learn from the world around. Same thought process, same design compromises etc. in all fields of human endeavor and nature…
WHAT TO LEARN NEXT?
- Design patterns
CONFUSING delegate BEHAVIOR
The commenter went on and posted a code snippet that was supposed to show the major drawback of delegates as implemented in C#. He asked
Just by reading this code, what do you think will happen? An exception? Print 5? Print 6?
private Duplicator Hook;
void Main()
{
Hook += DuplicatorImpl1;
Hook += DuplicatorImpl2;
Console.WriteLine(Hook(2));
}
static int DuplicatorImpl1(int number) {
Console.WriteLine("Impl1");
return number + number + 1;
}
static int DuplicatorImpl2(int number) {
Console.WriteLine("Impl2");
return number * 3;
}
public delegate int Duplicator(int number);
This code has a confusing output
Impl1 Impl2 6
The confusing output is not a bug in the .NET framework. This is an implementation detail. A detail that can only be arrived at if one is willing to go deeper into the concept. You can criticize the implementation saying that it is not intuitive or whatever, but cannot say that the above code and its output is the reason delegates are bad. The following is the correction I made to the above code that fixed the issue and provided the desired output
private static Duplicator Hook;
static void Main(string[] args)
{
Hook += DuplicatorImpl1;
Hook += DuplicatorImpl2;
foreach (Duplicator hook in Hook.GetInvocationList() ){
Console.WriteLine(hook(2));
}
Console.Read();
}
static int DuplicatorImpl1(int number)
{
Console.WriteLine("Impl1");
return number + number + 1;
}
static int DuplicatorImpl2(int number)
{
Console.WriteLine("Impl2");
return number * 3;
}
public delegate int Duplicator(int number);
}
What is needed is a GetInvocationList()
. Now, the output is exactly what we desired:
Impl1 5 Impl2 6
The important thing to understand here is that programming languages differ from each other not just because they have differing syntax, rather they differ from each other because their designers were influenced by different schools of thoughts. Software Engineering is not just about technical terms, keywords, syntax, IDEs et cetera. It is as rich and as vast as any other subject.
Language Design (a part of Software Engineering) is a delicate art of balancing purism and pragmatism. It is influenced by many subjects and it influences many. Programming has strong philosophical underpinnings. To ignore it is to clip your own wings.
Any wonder that Sun Microsystems and Microsoft went into a full-fledged virtual war over their differences in tastes.
GOING DEEPER INTO delegate
TERRITORY
The comment thread further evolved and many new thoughts were shared
What is going on here? We had delegates. Delegates worked just fine. People could (still can) do a lot of stuff with delegates. Delegates are the backbone of the event pattern in C#. They were more than enough. Why did things like Action and Func come into existence? How do they benefit the user?
Using a plain old delegate involved the following steps:
- Declare the delegate
- Instantiate the delegate and assign it to the appropriate function
- Call the delegate
Every single person in the C# universe has to do all of the above. There is a lot of verbosity here. This verbosity is also known as boilerplate code
In computer programming, boilerplate code or boilerplate is the sections of code that have to be included in many places with little or no alteration. It is often used when referring to languages that are considered verbose, i.e. the programmer must write a lot of code to do minimal jobs.
Action, literally, is a “thing done or an act”. Action delegate, thus, encapsulates a method that has no parameters and does not return a value. There are Generic versions of Action also available. If you have no idea what Generics is, fret not. For now, just look at how a List in C# is used and follow the same. Do not forget to learn Generics as soon as possible. They are wonderful (maybe, I will write about them later). Func delegate, on the other hand, is analogous to Mathematical functions in that they encapsulate functions that take one or more input parameters and return a value. The most basic Func
In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express. It makes the language “sweeter” for human use: things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.
Syntactic sugar is what you apply when boilerplate code starts making your code bland. This may be provided as a feature in the library or framework you use or you may yourself, with some experience under your belt, go ahead and manufacture various flavors of syntactic sugar.
CONCLUSION
Everything we do is based on what we have learnt from the world around us. A Software Engineer works in a domain that is more abstract than most fields of human endeavour. There is almost nothing tangible when we start working on a project. Nature’s repository of tricks is what we can bank upon.
Make mental models by observing the world around you. Have models ready before you start making your way through the syntax jungle.
Read Biology texts (just look at how piankillers work), Mathematics, Mechanics…