Tuesday, March 11, 2014

Design is not code behavior and may not even be how the code works in reality

People sometimes use the word software design in a promiscuous manner. Pretty much anything and everything fits into "design". That is not really true. In reality, design is a lot of things and none of them may directly relate to the code - it influences the code, rather than having a direct relationship. I say this because the design may say X and the code may wrongly do Y. Then it is the flaw in the code and not in the design.

What really comprises of software design?

Design is really a set of ideas which are noted down when thinking about the implementation.

- The set of assumptions which you consider when thinking about the implementation. This is the world view of the code. If a new assumption comes along, that can make or break the existing design.

- The goals of the project - "ultimately when all is said and done, this is what the implementation will accomplish".

- A good design may mention stuff which goes tangential to what a developer may think. A good example is in a recent project "by design", the assumption was that the database rows cannot be fully trusted because they could be out of date w.r.t the file system - so they database rows can only be used as an indicator that something may need to be done. The final word needs to come from the file system.

- A design artifact may not always be re-discoverable by reverse engineering the code. In most cases, it may take months of looking at the code behavior to assume that "one of the original intents of the coder may have been to do X and Y". Even then we cannot be fully sure about it. Hence, it is always important to have a design document, because without it, we cannot add or modify to an existing design without risk. The code could be wrong w.r.t the design document, but still it is important to know whether your thoughts are in alignment to the original designer of the code if the major project goals and assumptions remain the same over time.

- A design always considers the high level interactions between the various entities in the system. This need not always be software modules which are written by the developer implementing the project. It could be third party stuff like OS, IIS, file system, database, etc.

- The two minimally required UML artifacts for design are Use Case Diagrams and Activity Diagrams. UML makes it easier to go about designing a complex project where we cannot think of everything "at once". It helps us split the work in a structured fashion into small "bite sized" pieces which are easily understandable and digestable.

Friday, January 24, 2014

Using the concepts of nature in software

This post came about when my wife told me that she was playing a certain game in iOS and nobody she knew could go past a certain level in the game. She tried herself and her nephew also tried and failed. My son who is 4 years old and knew nothing about software or games was able easily go past that level. The reason he was able to do that was because he approach was random, and when he tried a burst of random approaches with no particular logic, he was able to break the level.

There is a software analogy to this as well. When any software gets mature, we tend to frown upon major changes to it, or someone "breaking the rules" and writing code on it. Every architect has been on this side at some point in the career. I have done this myself in cases where I knew for a fact that this would break something. I have also vehemently objected to a requirement which would cause me to break a lot of code while trying to implement it.

In software as in every other field, we tend to frown upon failure of any sort. If we worked on a project and it failed after 6 months, or was unable to attain what we wanted it to attain, this is considered a failure as well.

Is it really?

Today I feel the opposite. Everybody is different. When we tighten the rules and put a straitjacket around a framework or software, we are basically preventing any kind of out of the box activity from happening. We know this is difficult to do, or it breaks something - but how do you know that for sure? - maybe you failed to do something in a certain way - someone else maybe able to do what you were unable to do.

How would you know what was possible, if we prevent someone from trying out a different approach?

Sometimes, even when a different approach "fails", a lot of different things come out of the effort improving the code base in ways we never thought possible - because nobody every looked at the code from that particular perspective before.

Perspective is everything when it comes to large code bases. Because beyond a certain size, it becomes too much for one person to internalize. At that point of time, perspective is the only thing through which you can gain understanding of the system. When it changes, the understanding changes, and even evolves. You find over time that what you thought impossible yesterday is very much possible today.

And none of this is possible, if you never tried out a new approach, or went down the risky path.

A concrete example of this is when I had to add a new mode to an existing software which made it more reliable, but also quite slower than before. I tried out a very different approach to make this as fast as possible - basically indexing files beforehand, instead of doing it when the process was running. This was not enough - so I had to go around and make other parts of the code run 10X faster than before, and utilize multiple cores. The end result was that even if in the new mode, it was as fast as the earlier mode or maybe a bit slower, if you ran in the old mode, it was 10X faster.

If I had never tried the new mode saying it was too difficult to do, I would never have improved the old mode, and found hundreds of small but important things which made the software better in the long run.

How is this related to Nature?

We write this simple, cave-man software and prevent others from trying out new things, or worry about risk. But look at nature which has created plants, birds, mammals and even the human brain. We have reached this stage in evolution because Nature tried every random permutation and combination based on the given parameters and optimized along the path which is just right to reach the current stage today.

It tried all approaches, all risks. Entire species dies, new ones arose, but it still went forward and continues to move forward.

Random approaches and failures are necessary as they provide us with as much information as the successes do. Only through learning both success and failure can we really move forward. Infinite patience is required to find the right solutions for difficult problems. Solutions are not just found through brute force human intelligence or infinite CPU power.

I think a major failure of current software is that we build something and we tighten the rules as we go along, and then new ideas have to create something new from scratch. We never continue to build upon the same thing, evolving it over time. So, we are forever going in a circle, reaching nowhere because of this. All logic is useful, whether it was written in FORTRAN or PASCAL or C++.

Discussing these ideas with others, they mentioned that this might be the reason that in some companies on the west coast, they tend to keep only younger employees and not experienced ones. Because, even though experienced employees are technically competent, they are highly resistant to change and unmalleable to consider new approaches and risky techniques. So, companies keep younger employees who don't know enough not to take risky approaches.

Whatever be the case, I can't generalize that older employees are always resistant to change. They have strong opinions for sure, but I have worked with very constructive senior developers who embrace the fact that we are building something and every time, this has resulted in massive success. On the other hand, I have also worked with very capable senior developers who are very resistant to change and very nonconstructive in terms of showing only negativity when faced with having to build anything new. There have been teams which are highly "aligned" to resist change, even building frameworks which actively straitjacket any new approaches to code. A particular instance was a service architecture where the code would error out if you returned an object with two properties instead of one, or you had to duplicate a class in two assemblies deliberately just to be able to write the service layer code.

Friday, October 11, 2013

The Code Paradox

It is kind of interesting for me to note that when the code is really bad and very buggy, making small changes to it, does not affect its overall quality a lot. In many cases, defects combine to get something working together in a certain way.

What is more interesting is that when the code is quite good, well written, modular, reusable making small changes to it can actually break everything. So, it affects quality a lot more, and small things cause more stuff to break especially because of reuse.

This could be entropy. The natural state is disorder and it stabilizes there.

When we try to bring order, that is an unnatural state. So, I have seen many, many cases where the code is really good, and someone unfamiliar with it makes a slight change and it keeps breaking all over the place.

The real challenge here is - how do we write software which has entropy by design but still works well? - because I believe biological systems which work well with the most diversity and population are like that.

Is it not weird that even in software, which is totally man made, this basic law of entropy remains true? - just shows that in a system where entropy is the law, even for seemingly autonomous entities (like us) who create new things - the things we create however abstract they are also stabilize at entropy.

Embrace the chaos?
Embrace the diversity of thought which makes a single code-base have both good and bad code?

Definitely something to think about.

Wednesday, September 18, 2013

Lesson 7: Micro level understanding does not translate into macro level excellence

This is an important topic because the number one problem which tech companies face is that they are unable to figure out who to select in an interview. Most of the time, the selected person is unable to fulfill even a percentage of what they thought she/ he could accomplish.

There is a good reason for this anomaly. It is because in the computer industry it is widely assumed that just because someone has understanding of microscopic details regarding computers, some of which maybe arcane, that translates into excellence at the macro level.

From my experience, this has been proven wrong again and again, and I have learnt even from my experience hiring people.

Basically, the theory goes on that say if you understand computer science concepts like data structures, or stuff which is really important at a system level (like operating system concepts, and other details about compilers, linked trees, etc) then wow - you are a find! - if we hire you - the next day you will be productive.

On the other hand, if the guy does not even know data structures, and cannot solve the complex b-tree problem posed in the interview, then he must be a total loser.

This is total nonsense. As Einstein said: "The true sign of intelligence is not knowledge but imagination." - I saw this quote in my kid's kindergarten. Maybe we need to send such people back to kindergarten. What I've learned over the years that it is very unfair to think that just because someone has not done certain work or does not know some concept he is not a good developer. It is now about how much you know. It is about how well you know what you know. Otherwise you end up in the situation where the guy can talk about any subject for hours, but cannot get any specific thing done, or gets confused and ties himself up in a straitjacket when trying to solve a real world problem.

Here is a post about how not knowing a design pattern does not mean you do not know how to code. If the guy is good and designs software himself and does not know design patterns, it probably means he has more successful software projects under his belt.

I was once interviewed at a company like this, where the manager initially rejected me. For some reason, after seeing my experience and talking to me, his manager hired me overriding him.

Then I joined this company where everyone had an MS in computer science, and I was the BS in Applied Electronics and Instrumentation.

I did better work than anyone there... period... without doubt...

Why?

Because most of us are application developers who work at a much higher level in the code. In my case in C#. We work within the bounds of the .NET framework. What matters for excellence here is your experience working with the framework, and how much you know about it, and how much your mental model is for working at this level.

They hired a russian guy who was a colonel in their army who was the best guy who ever passed this "classic" interview. He knew every single thing and more about systems.

He could not understand my code. In fact, he was not even able to maintain it. All I did in my code was extreme code reuse, with generics and not very complex OOPS. I don't claim to be a OOPS guru either.

I need to tell this story to convince the reader categorically that this entire theory is false and very foolish. I am the living proof of that.

To be a great developer, the number one thing you need to do is to write a lot of code. Gain experience. Try out the right and the wrong approaches. When you do this for a long time, you gain an understanding of how the framework works at its level. Then you can do great work with the mental model you have - there are lots of stuff which people who know more concepts, or can do b-tree cannot do.

In 90% of the cases, you will never need to know the b-tree to write excellent code, which surpasses all expectations. In the rest 10% of the cases, by then you will be smart enough to know you need the b-tree and you can handle the condition.

In 99% of the code, programmers do not have any macro level understanding of the code. Hence, they often never reach the level of intricacy where a b-tree knowledge would even help them. They are still struggling to figure out how to show alternate rows in different color on a web page - forget about going any deeper.

I have seen code of the people who know b-trees and other stuff, here is what I found:

1. They don't know how to do code reuse properly.
2. They don't know how to user interfaces properly.
3. They are confused as to when to use loosely coupled code and when to have highly cohesive code.
4. They use the unity to make all their code run via configuration.
5. They tie themselves into a knot, and struggle to free themselves and basically the knot keeps getting tighter and tighter.

Another example of a total asinine belief is that if you cannot write a sorting algorithm from scratch, that means that you cannot be a good developer and "solve the problems we have". Basically, the simple sorting mechanism you wrote cannot scale to 100K rows. This is total bullshit. The reason is because in today's world, you do not need to write a sorting algorithm from scratch anymore. We have inbuilt methods in the frameworks which bypass the need to know an algorithm like that.

If the framework does not support it, in the weird universe you live in, this is still a really asinine idea because you are doing something wrong if you have to write C# code to sort 100K or a million rows. This is why there are databases and they have the sorting support in SQL. For those who say what about in-memory systems, my answer is that your in-memory system will not scale on the cloud, if you have one object with a million rows in it - how do I know? - I've explored the limits of caching in the cloud and came to the conclusion that in-memory is just good for key-value pairs. Sorting like you want to do, does not give good performance at that kind of scale - the paradigm is to avoid doing it. Even if you want to do it, you got to use a totally different algorithm to divide and conquer the work fog big data.

Also, let us look at the biggest reasons why projects run by such people always fail and never get delivered on time - this is called premature optimization. You spend so much time prematurely optimizing your solution that you end up finding that you need to rewrite most of the code because what you spent so much time coding is not useful anymore. The best way to write successful software is to keep it simple and unoptimized  to begin with and optimize over time as you find out that what you wrote meets the need, but does not scale, or is not elegant enough (or whatever). In the real world, you will possibly find a real use for these kind of optimizations way after the product has gone out, and become really mature and there is a real need to say use some of these algorithms. By then, you will know the system so well, that you can apply the optimization in the best possible manner.

This is the reality of the world we live in. Physics is a good example of this. On one hand you have Newtons laws which work at our physical level, and then you have the modern theory of relativity which works at the sub-atomic level.

In computer science, we are making the mistake of assuming that just because someone is a PHD in sub-atomic physics, he is automatically a PHD in Newtons laws when in fact, he may not even pass the middle school level of Newtons laws. Newton's laws are simpler than sub-atomic physics, just like macro level programming knowledge is simpler than knowing systems level knowledge - however, it belies the fact that just because it is simple does not mean that if you have no idea about these laws, you can still solve problems at the level of the physical world.

There is a whole treasure trove of knowledge at the macro software level which is not in any book, or in any design pattern. People who know this from experience and use these laws, are the great programmers today - others look at them and wonder how the person is doing such good work, when he does not know anything about data structures.




Friday, January 6, 2012

Events and Configuration State

I found another interesting application for configuration and state recently.

We were discussing events. The user presses a button, a request goes to a service, and it starts a process. They were saying the start event is when the user presses the button, and the end event is when the service finishes processing.

Wrong!

The user pressing the button is a command - a configuration. Events are related to the service starting something or finishing something - so it is state.

The correct design is that when the service starts processing the command to start doing the work, then its state is "started" - so that is when the event should be fired. Same for when the work gets done.

Monday, May 9, 2011

Dealing with Ambiguity: Zero does not mean anything

In software we often come across complex and difficult scenarios which might muddle our mind. One of the key concepts to understand is how to deal with ambiguity.

Basically, this means that if the data we have is not adequate to reach a satisfactory conclusion, then we need to generate the additional information which is necessary for the computer to understand what needs to be done. This automatically means that assuming that something needs to be done in a certain way is a wrong thing to do. In such scenarios, always have extra properties which can go down to the most basic level and which can be parsed to clearly understand the requirements.

A simple example of this is how we deal with nullable types, enums, etc. Enums make it easier to assign a meaning to an esoteric number which is unreadable and not maintainable. Nullable types for values like dates and numbers allow us to know for sure that, there was nothing present to begin with. In a similar manner, we often abuse zero to mean something in applications. Zero is zero, it means nothing, a number can be initialized to zero, to begin with, so, even if we use nullable types, it would still be good practise to say that 0 = default = nothing.

What can be more interesting than the fact that, all these concepts even boil down to simple naming conventions. I have seen reams of code which are completely meaningless start making sense, after I renamed "xyz" to something more meaningful like "index" or "counter" or something else. In this case, the name of the variable was making the usage ambiguous. I gave it a concrete name and everything simply fell together in place.

Another interesting aspect to this which confuses many engineers is that most of the time, when we feel ambiguous about something, it is because we do not have enough inputs. The existing inputs are not adequate for the program to assume something and then exhibit a new behavior, or execute some logic. Most often in such cases, we need to pass these new inputs or parameters right from the user end, through the various layers to the code which needs to decide what to do. And it is difficult for a novice engineer or even someone with experience to realize that ambiguity is tackled by removing it, and one of the ways of doing that is to add more inputs.

I was having a conversation with a friend of mine regarding a very complex problem. I understood only part of the detail, but during the conversation, it became apparent that there was an underlying ambiguity regarding what decision had to be made, and my friend was struggling to come out with an assertion that so and so would mean X and so and so would mean Y. I interjected and said that, the situation is ambiguous and that assertion cannot be made. We added one more column which would say concretely what would result in X and what would result in Y.

Sunday, April 10, 2011

Configuration & State in Web Service API's

I was recently commended on the strength of my API design, and there was a remark that elsewhere we did not do it the way I implemented my method. So I thought that I would like to add a concrete "modern" example of configuration and state considerations in web service API's.

Basically, we have an API which lets the customer send us "activate alarm" requests, when the alarm is activated, the device sends back the alarm status. When I designed the API, there was the thought that, why not simply use one database column for both considerations - "what the customer asked us to do" & "what the device told us it's status was".

The reasoning was that, if we are in fact immediately sending the device an alarm activation message, why, it would go into alarm state. Why have extra fields to capture this information?

I put my foot down. Nope, Nada - we won't do it that way. the activate alarm request is user configuration, the state of the alarm activation in the device is a separate entity, both are different and should not share the same storage space.

8 months after the API was deployed, we had a customer issue which we were able to easily debug because we were storing the values separately. That turned out to be a device issue.

Elsewhere, we shared data space, and there, now everyone is in a tizzy trying to fix that entire implementation.

So.. long story short - keep configuration and state different. Always.