The Default: Differences
You are unique. No one on this planet is like you. You are special. This is pretty much what we hear a lot nowadays. People are individuals, and as such are unique and special. I’m all for that, but it puts us in a mindset to think about others as different. However, this mindset can be dangerous or, in the very least, not productive. Let’s turn things around and think of others as similar. Find something you have in common with others and focus on that. As it turns out, such thinking makes it easier to work with people and also has benefits while coding! We’ll have to change the way we think though.
Unfortunately, we are tempted to look at other people and things and declare them different. A most blatant example of this is sexism and racism. We see someone of another sex or color and immediately think of them as different, so different, even, that they are not worthy of respect. Even if we don’t necessarily have negative feelings towards the other sex or race, we do declare them different. We also have ingrained stereotypes that sharpen those difference. Women are not good at math, have strong emotions, and they play with dolls. Men are technical, strong, and they like sports and cars (by that definition, I’m not much of a man by the way).
Actually, we want to be different so badly that even if we have the same interest, like football (the European or American kind), we find a way to differentiate ourselves by cheering for other teams and get really religious about it (to the point where we literally bash each other’s heads in).
NBC News journalist Linda Ellerbee once stated on the subject, “People are pretty much alike. It’s only that our differences are more susceptible to definition than our similarities.”
We judge people all the time. We make assumptions based on their hair, their clothes, their car. When we listen to music, we like to think of classical as different than rap and metal. Likewise, rap and metal are nothing alike. A horror movie is nothing like a romantic comedy. We label people and things to separate them from each other.
The Importance of Similarities
Let’s try to change all that. Stop the judgement and look for similarities instead. We are all just people. We’re trying to make a living. A lot of us are trying to improve ourselves. Some of us like classical music.
That manager that makes your life miserable and is absolutely nothing like you is probably trying to provide for his family just like you. If you met him at a bar, you may even find out he likes the same music as you. You may not like him for it, but it does make him more human; it makes him more like you.
Let’s turn things around. Why is this manager making your life miserable anyway? Probably because the manager sees you as different from himself. He’s a smart leader and you’re a dumb slave valued employee. There’s actually a good chance that once the manager finds out you listen to the same music, he gets a lot friendlier. Suddenly you’re not so different anymore. Likewise, you may be more open to his suggestions. How wrong can this guy be if he listens to your favorite band anyway?
We’ve all seen people come together over a shared interest and grow apart because they’ve lost the common interest. Heck, we’ve all been one of those people!
You’ve probably heard the phrase “nothing binds people more than a common enemy.” What if it’s not the common enemy that binds people, but the common interest in defeating that enemy? If a common interest binds people, it could just as well be music or sports.
I have been best friends with one person for about 16 years because we both used to cycle to school (instead of taking the bus). Currently, I have two colleagues who sometimes take the bicycle to work. It’s one of the very few things we have in common, but it’s good for one or two conversations a week! A similarity can be as simple as taking the bike.
It’s Basically the Same Thing
Imagine yourself talking to someone—let’s call him David—at a party. David mentions he likes jazz. Bummer, you like heavy metal. Not all is lost yet, though. Again, we like to think of things as different, and jazz and heavy metal are pretty different at first glance.
Well, you just so happen to know that the drummer of a notorious black metal band is actually a jazz drummer. Then there are bands that actually mix metal and jazz. Also, both genres can be pretty technical; the musicians must really master their instruments. In fact, you’ve been in heated discussions over who plays faster: a jazz drummer or a metal drummer. Knowing this, are jazz and metal really so different? While you see lots of similarities, you’re still not much of a jazz fan. However, you can still congratulate David on his good taste in music.
Let’s look at another example. Mary collects stamps. Can you think of anything more dull than collecting stamps? Mary is a big nerd. Luckily, your Harry Potter merchandise collection is much more interesting. Guess what, big nerd, you and Mary like the same thing: collecting!
Paul is a dog person. You are a cat person. You’re both “fluffy pet companion” people.
Susan drinks coffee. You drink tea. You both drink a specific, oftentimes caffeinated beverage. I’ve actually bonded with people because we were both drinking: the other beer and me a Fanta (as I don’t drink beer at all).
None of this is guaranteed to make you a better talker, more likeable, or even slightly more social. In fact, I’ve never been a people person. Jazz and metal people can turn out to be very different after all, including music-wise. A dog person may really hate cats and other pets in general. And a stamp collector may think you’re the loser for collecting stupid Harry Potter merchandise.
Overall, though, looking for similarities helped me to like others better, making it slightly easier and more enjoyable to talk to them. I’m convinced the world would be a better place if everyone did it. So does Swedish chemist Alfred Nobel who said, “One can state, without exaggeration, that the observation of and the search for similarities and differences are the basis of all human knowledge.”
However, it seems there’s another benefit in thinking in similarities rather than differences.
The Importing Process
Imagine yourself sitting in a meeting with some colleagues to discuss a new feature for a customer. The customer has requested automatic importing of some data for sales orders coming from his customers.
Without knowing anything about the files or the data, Alice proposes a single importer service that can read the third party files and write their contents to your customer’s database. At this point, Tom points out that’s never going to work because he knows every customer has their own file format. There are a few comma-separated text files and some Excel files. Clearly, each file is a totally different beast and should have their own service to handle them.
At this point you decide to step in and point out that having a different service for each file creates lots of maintenance overhead and probably duplicate code as well. A single service would be better as long as it’s configurable to support different file formats and layouts.
Tom throws his hands into the air. “Fine, but don’t come to me when you’re dealing with giant if-then-else branches to handle all the different files!”
Does that sound familiar to you? Maybe you’ve actually been Tom in that story, or maybe you still are. Tom’s reaction isn’t wrong, but what he fails to see is the similarities between the different files. They’re all files that need to be read, and produce a uniform output that should be written to a database. Of course, as programmers, we learn to think about edge cases. We go straight to thinking, “This won’t work because…” but there are other solutions. For instance, have we thought about files other than text and Excel and how we will fit those into the single service? What if we get files that contain data other than sales orders?
On the other hand, we programmers tend to think in abstractions. A file is a file no matter if it’s text or Excel or what have you. If we want to be scalable we can’t take each file format into consideration. Our system deals with Customer A or Vendor B, and we don’t care who A or B are because the system will treat them the same (unless you’ve written something like if (customer.Name == “Contoso”) { … }).
Before we get into the technical details, let’s apply the previous section on Tom. He clearly has a different idea on what needs to be done than we have. You could team up with Alice and get into the “us versus them” mentality. A single service versus multiple services. Or you could find some common goals between Tom, Alice, and yourself. Obviously, you all want the best importing process you can think of (given the time and money available). Perhaps, though, you were already thinking that Tom disagrees with you, so he must be trying to sabotage the importing process?
No, Tom is actually doing his best to get the importing process as good as he can get it. He probably has different knowledge and experiences than you have and thus comes up with another solution to the same problem.
Now, since you and Tom have the same interest, you can have a constructive dialog instead of a religious war / flaming contest. Make sure Tom sees it that way, too. “Tom, you and I want the same thing: to get the best importing process we can think of. Here’s why I think we should have a single service…” Be sure to mention the goal you have in common. Tom may now agree with you, or maybe he had a different goal after all! Here are some possible responses:
- “Yes, I want that too, but here’s why I think we definitely need multiple services…”
- “I’ve never done it like that before, but you sound convinced. Let’s try a single service.”
- “Well, if you look at it that way, a single service may indeed be better. I was just trying to get something out quick and cheap!” (Here you didn’t actually have the same goal!)
Perhaps, now that you’re both in this together, you’ll even change your opinion on having a single service!
It could happen that all you have in common is the ultimate goal: get that importing process as good as possible. Maybe Tom wants nothing of that object oriented interface mumbo jumbo us youngsters keep talking about. Nothing like separate services and some good ‘ol if-then-else branches to get the job done!
Maybe he’s even an asshole about it. “You youngsters ain’t seen nothing yet! Get some experience before you tell me how to make a service!”
Maybe you don’t even share a common goal! Tom may tell you he wants the best importing process possible, but maybe he just wants to avoid work or avoid using technology he doesn’t know (making him look incompetent).
In that case, you can work with Tom or simply discuss it with your manager. Tell him you can’t work it out and that he should have the final call. You and Tom probably aren’t going to be friends, and you’re about as incompatible as his stupid services are going to be.
Similarities in Software Design
In the next stage of the importing service process, Tom mentions giant if-then-else branches, which sound about right to use since we do get multiple files that need to be handled differently.
But wait a minute; all files have in common that they need to be handled to produce a uniform output! Sure, the handling is different, but other than that the process is similar. If you’re an abstract programmer, you’re probably already thinking of some IFileHandler interface. How, then, would we know how to handle each file?
Certainly, we need to be able to tell one file from another. Maybe the files are prefixed with a customer code or maybe they’re saved in a customer specific folder. Using that logic, we would know that A_{datetime}.txt or C:\Imports\A{datetime}.txt is a file of Customer A and B_{guid}.xlsx or C:\Imports\B{guid}.xlsx is a file of Customer B. Anyway, we need to know how to handle a specific file or nothing is going to work (not even Tom’s “service per file” solution). However, if we know our customer, we know how to handle it (that’s the configurable part).
I’m going to take a bit of a leap here, but here is some code that might handle our files. You can start a new C# console application and copy/paste the code; it should work and produce a correct result.
Of course I can’t write a full solution here, so it’s very basic, but you should get the idea. There are two customers that each have their own file format. The files are picked up by the system and the actual reading and parsing of the files is delegated to an IFileHandler. The handler returns a SalesOrder object which can be processed further by the system. As you can see, I’ve created a single file handler for each customer (kind of like a single service per customer), but you can add extra layers of abstraction. In a real world example, I’d probably have a TextFileHandler and an ExcelFileHandler and have the customer specific handlers inherit those. Or, even better, have a generic TextFileHandler and ExcelFileHandler and make them configurable per customer.
Being abstract is something profoundly different from being vague… The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise. —Edsger Dijkstra
Pretty pleased with this quick example, you go to Tom and tell him this should do it in a single service. What does Tom say? “See, I told you this isn’t going to work! You can now only import sales orders, but I know some customers already want to import other data too!”
Think about all the data that could be imported: orders, customers, addresses, products, phone books… Basically anything that fits into a database. Again, think about what they all have in common. They’re basically just tuples, an ordered list of values. Now you could think about returning a DataTable or anything else that represents generic sets of data. Speaking of generics, how about an IFileHandler<T> where T is the return object (SalesOrder, Product, Customer…). Surely, this requires some technical knowledge, but it all starts with seeing the similarities in each file.
Now what if you had to calculate a discount over the order? There are multiple types of discount. 1 dollar discount on all products over 5 dollars, two for the price of one, quantitative discount, 20% discount, coupon discount, etc. Now, rather than thinking of all discounts as different things that all need to be calculated differently, think of them as a single step in the process of calculating the final price. The details of the step vary by product, but the step is still calculating a discount. What you will still need to think about is if discounts stack and, if they do, in what order they have to be calculated. Again, we could make use of an interface. Here comes quite a bit of code, but you can see that adding discounts is actually a piece of cake.
We can implement a new IDiscountCalculator, add it to the currentDiscounts, and it will be applied. Also, if you want to apply multiple discounts, you can simply implement an IDiscountCalculator that aggregates other IDiscountCalculators. The most difficult part is finding out what discounts apply (probably stored in a database) and dynamically creating the appropriate IDiscountCalculators (perhaps using a Factory).
To me, it’s a lot better than writing huge if-then-else branches that need to be edited every time a new discount is introduced. It’s testable and open for extension without having to touch existing code. It also becomes a logical design once you start thinking of discounts as “the same thing,” whether it’s by percentage or something else.
Once you start to think in similarities, software abstraction comes more natural.
When Things Are Not Similar
If we take a look at the file importer, you may conclude that once you’ve abstracted everything out, it’s much more than an importer. It could translate text to Excel, send out emails, and walk the dog. Likewise, the IDiscountCalculator can do just about anything with an order. Beware, though, too much abstraction can be a bad thing!
Each layer of abstraction is a layer of added complexity. Sure, you can decouple the reading from handling from the writing (which is probably good practice), but once you find out your import service is now also doing all of the exporting (which it could totally do), you may want to take a step back and think if this is what you actually want.
It may not be a problem per sé, but keep in mind that if you need to restart your import process for whatever reason (maybe a software update), you will also restart your export process. Likewise, if you make changes to your export process, your import process may break as a result. In this case I’d go with Tom and create two separate services, one for importing and one for exporting (that may or may not use a common base class or library).
Earlier, I’ve written a blog for SimpleProgrammer called KISS – One Best Practice to Rule Them All. From a software perspective, KISS, keeping it as simple as possible, should always get priority over finding similarities.
For example, I’ve worked on a WinForms application where a couple of forms looked exactly the same. Your first instinct may tell you to create a base form and inherit it. You’ve successfully found the similarities in the different forms and you’ve applied the DRY principle (Don’t Repeat Yourself). Unfortunately, form inheritance doesn’t work all that amazingly. If I had to do it again, I’d probably copy/paste those forms in favor of KISS, especially if only the user interface was similar.
Don’t stretch it. You may think an order is an order, whether it’s a sales, purchase, or production order. Those orders may have a lot in common, but they’re probably used so differently that treating them as the same thing (even partially) will have little or no benefit and will create problems later on.
Actually, there’s an object oriented design principle that addresses this issue, Liskov’s Substitution Principle (or LSP, the L in the well-known SOLID principles). This rule states that implementations of the same base class should be interchangeable. The classic example is that a rectangle is not a square because they behave differently.
In the example of our file importer, we could easily replace Handler X with Handler Y, as long as the file has the proper format. For the system as a whole, it doesn’t really matter which file gets imported; they’re all imports anyway.
For more information on how to design your software for reusability, separation of concerns, and loose-coupling, you can check out my free ebook, Object-Oriented Programming in C# Succinctly.
Obviously, it takes some experience and technical knowledge to make the right call when things are similar and when they really are different. Unfortunately, you may never know if you did make the right call. Perhaps our importing service will never get more than those two files or will get millions of them every day, making our service slow and hog up resources. Had we listened to Tom it wouldn’t be a problem to handle both files on different servers and balance the load.
What you will know is when you’ve made a bad call. When things that should operate independently always go down together or when you need to update multiple services for a single change, you know something is not as it should be. Fixing such issues once the damage is done is often expensive and time consuming, so it pays to think it through up front.
Fred Brooks, a software engineer, said it best: “Good judgement is the result of experience… Experience is the result of bad judgement.”
Change your life today; start thinking in similarities!