Header Ads

10 Design Principles For Software Engineers

10 Design Principles For Software Engineers

So in this article, I'm going to be sharing with you 10 design principles that all software engineers/programmers need to know. Now, these principles relate to how you come up with the design for a system, and also how you implement specific components and how you should write your code.

They're not things that you absolutely need to live by, but they're just things that you want to think about when you're designing a system or when you're implementing a system because they're going to make your life a lot easier in the future if you do decide to follow them.

10 Design Principles you need to know

1. Increase Abstraction

It is for increase abstraction. Now, something is more abstract if it's kind of a general term, a general idea, and it's not tied to direct details or two examples. So for talking about this in a coding sense, something that is more abstract is just more generalized. If we're talking about a function and we wanted to make it more abstract, rather than taking in say a triangle, we would take in a shape, right?

Because they, the triangle is kind of a more concrete implementation of a shape. Whereas a shape is just this general idea. And it maybe has some guidelines and some things that need to be consistent across all shapes, but we're not concerned about the details relating to the shape. We don't care if it's a triangle, we don't care if it's a rectangle, we don't care what color it is.

We just care that maybe it has an X and Y property on it. Maybe we care what the area of it is, right? There are all of these different things. And that's kind of the idea behind abstraction. So if we keep going with this example of the shape, if we had a function and we wanted this function to place shapes onto the screen, well, I could write four different functions that place each individual shape, or I could write one function that takes in any shape.

And we would just say, okay, anything we're taking in here must have an X and Y attribute on it. That's all the way we don't care about the details relating to the implementation of that shape. We just care. It has these few properties. Then we would use those huge properties to actually place the shape onto the screen. And that would be a more abstract design.

2. Design For Flexibility

What this means is to anticipate the fact that your system is going to change in the future. That right now your system might be relatively simple, but in the future, it may get much more complex. You may want to add a lot more stuff. You may want to remove this. You may want to swap out this implementation of an object or an item or whatever it may be.

And you want to design with that in mind. A lot of times people design the system that they're working on today that they're going to use right now. They don't think about the fact that in a month or so, or maybe in a year, maybe they're going to scale this project up or scale this platform. And they're going to need to add this feature, add this component.

And if they didn't design the system with that in mind, it's going to be very difficult to do that. And oftentimes you see people just redo the entire code base or read it the entire system because they didn't think of that in the first place.

And now it's just easier to come up with an entirely new design than to try to modify it or change the existing design. I'm sure you guys have had this before. You've been working on a project, you get near the end, and then you realize, Oh, I need to add this thing in.

And you really want this thing in. And then you realize based on the way you've done things, that's impossible. You just can't do that. And so you have to rewrite a ton of code redesign stuff that you've already worked on. That costs a lot of time. And obviously, that's just a pain. You don't want to do that. So design for flexibility.

3. Design For Testability

This is something that I'm very guilty of, that I do not do in almost all of the projects that I work on. And that's usually because these project, sorry, are smaller and kind of hobby projects, but designing for testability becomes very, very important when you're working on large-scale systems and large code basis. So I'll give you a real example here.

When I worked for Microsoft, there were over 6,000 tests that ran on every single poll request that you submitted to the repository. It would take about two to three hours to actually run these tests. And the reason for this is that they needed to make sure that whenever you submitted some new coat, you were trying to change the master branch on the GitHub repository, that you weren't going to break anything.

And the reason they were able to have that many tests is that the developers who wrote this massive codebase designing codebase in a way such that they were able to test the stuff that they write. So if you write a bunch of code and you're in a huge code base and it needs to be tested, you want to make sure that the code actually is able to be tested. And you might ask me, well, how do you do that? That really depends on what you're writing.

So I can't really answer that question, but just something to think about when you're writing code, will I be able to actually test this? Is there a test case I can come up with? Is this a functional test? Is this a unit test? What do I need to do to have a test for this code? And is there a way that I can maybe change this code such that it's going to be easier to be tested anyways, something to think about.

4. Design For Portability

What this means is to anticipate that the design or the system you're creating right now may potentially be used on a different platform or device than you're currently targeting. So if you're making a web application per se, well, you could design it just for the web, but that means now, if you ever want to turn this into an iOS app, an Android app, maybe you want to turn it into like a windows desktop application.

It's probably going to be very difficult to do that. That may actually involve you redesigning the entire system or creating an entirely new system to be able to kind of port this over. So you want to keep this in mind when you design the system, will this work on Mac, will this work on windows? Will it work on Linux? 

Will I be able to make some maybe front-end user interface that uses the same backend system for an iOS app or for a web app or for an Android app? 

These are the things you want to think about because they cost you a lot. If you don't think about them now, right? If you're in the future, all of a sudden, now you have this huge platform, a ton of users and you want to make an iOS up and then you realize, Oh, I'm going to have to redesign the entire system. That's a pain. And you're probably going to wish you had thought about it for, you know, five or 10 seconds, uh, before you designed it, the initial system.

5. Divide and Conquer

This is one of the most core principles in really any problem solving or design system thing in general. And what this means is to take your large problem and break it down into as many small subproblems as possible. Solve those small, those smaller subproblems, sorry. And then that will help you solve the large problem.

The idea behind this is that large problems are hard to solve. They're complicated, there's a lot going on. And so to make it easier, you can divide these problems into smaller problems, solve those, and that in turn will help you solve the larger problem. They give you an example of this in a coding context, maybe you have some very large-scale system.

Maybe the system has some sub-systems involved with it. Maybe those subsystems all have different packages. Maybe those packages have different classes. Those classes have different methods, and maybe those methods use some external dependencies or some functions see right there. I just broke this entire problem down into a ton of different sub-problems.

And now all I need to do is say, figure out what all of the methods of the class are. Figure out what all of the classes of the packages are, figure out what all of the packages of the sub-system are. And then all of the subsystems of the system as a whole. And I'm good to go. I've pretty much solved the problem. So that is the design principle. 

6. Reducing Coupling

Coupling occurs when you have packages, modules, classes, files, whatever that are very interdependent on each other. So what this means is that you have some package and it relies on this other package. And if this other package has some major change to it, if something maybe goes wrong with that package, that's going to affect the package is depending on it.

And so having a system that is highly coupled, although it may make it easier to implement, uh, kind of the first time along is going to make it much harder to debug and much harder to fix if anything goes wrong in some of the coupled components.

And oftentimes you have such highly coupled systems that is actually very difficult to determine where the coupling is occurring, because you may have a package depending on a package, depending on another seven packages. And now you actually have no idea why maybe package one is going wrong.

When maybe it's something in package nine, that's like four levels of coupling away from it. So hopefully you're getting the idea here, but if you can make your components in your system as independent as possible, that's generally best to make it easier to debug. And really it's just a better design in general. You don't want to have things that are highly dependent on each other.

7. Increase Cohesion

Cohesion kind of means that everything that is grouped together actually makes sense. It relates to each other, and there's nothing in this grouping that kind of doesn't fit in it. Isn't cohesive. So for talking about this in a coding sense, what this means is designing your packages, designing your modules, designing your classes, such that everything is cohesive. Everything is grouped together.

That should be together to give you a concrete example. Think about the math package in a language, say like Python or in Java, JavaScript, whatever. If you have the math package, this is considered a very cohesive package because everything related to mathematical operations is in that package. There's nothing in there that's related to input and output bound operations.

There's nothing in there that's related to printing numbers, right? Everything that's in that package fits together. It makes sense. And it's grouped in a way where it's very organized. So increasing cohesion will increase the organization of your code and it will make it much easier to find things and just make the system simpler. It makes everything make more sense, right? I know I go to the math package when I'm looking for stuff related to math.

8. Anticipate Obsolescence

What I mean by is if you are using external dependencies in your project or in your system, you want to be very careful what dependencies you're using and if they could potentially become obsolete in the future. Now, something can become obsolete in many different ways. It could become deprecated.

It can no longer work for a specific version of your programming language. It may not be supported or maintained on say windows or Linux or Mac. It may not have any updates. May have bug fixes that just aren't going to be fixed. It may have poor documentation.

There are all kinds of different things that can go wrong, especially with things that you're not in control of external dependencies. And you want to make sure, sure that you don't have a project that relies on hundreds of external dependencies, because even if one of those things go wrong, if one of those things no longer works, well, you're kind of screwed, right?

There's not much that you can do. And obviously, that's not a good thing. So anticipate that things are going to become obsolete and make sure you're very careful in the external dependencies that you're using because of the reasons I just stated.

9. Increase Reusability

This is pretty straightforward and intuitive, but we want to make sure that whenever we're writing code, we're thinking about how we can make this code as reusable as possible, rather than writing say a super-specific function. That does one thing does one thing really well, but only works in maybe one specific implementation.

Maybe we spend a little bit of time and make it a bit more general, make it a bit more abstract. So we allow it to be reused in a different context, right? And this is kind of the idea behind reusability. Oftentimes you're making a small sacrifice up front. So that later on, you were code is a lot more reusable and saving a ton of time.

Because now you don't have to go back and understand this code, rewrite this code, make another function, make another implementation. You're going to thank yourself because the code you wrote before is just more reusable.

So reusability is a good thing you want to have as the least amount of code as possible. And a lot of these other kinds of design principles, we'll explain why that is a good thing.

10. Design Defensively

What this means is to idiot-proof your coat. That's really the best way that I can explain this. You have to imagine that anyone using your code, using your framework, using your classes, whatever is an absolute idiot. You just have to imagine that. And what that means is you have to make sure you have good error messages.

You're handling all of the invalid input. They're going to give you you're handling maybe wrong output or you're handling anything that could go wrong from the user calling or using your code or your function design it in a way such that it doesn't matter who uses this code. It will not break. It is robust. It is defensive, and it is giving the user valid error messages and trying to help them be able to use this framework. Right?

If you go in any programming language and you start messing around with syntax and you do things wrong, oftentimes you're getting pretty good error messages. You're being told exactly what it is that you're doing wrong. And although that might be a little bit cryptic to you, that does actually have a meaning. And if you will look that up on Google or stack overflow, you're usually able to figure out what it is that you've done wrong in your program.

The only way, or the only reason sorry, you're able to do that is because the person who wrote that code designed it defensively. They knew that idiots like you and me were going to be using this framework and have no idea how it works. And they designed it in a way such that it would help them use it if they used it wrong. So with that said that, and it's going to conclude this article on 10 design principles. You need to know.

And as I said at the beginning, these principles are not things that you always need to follow. There are always exceptions to these principles. The main idea here is that you need to be aware of these principles and be very intentional as to why you may be breaking them.

So always think about these principles. And if you do want to break one of them, make sure you can justify why and you understand why that principle may not apply to you in a certain situation. Anyways. I hope you guys enjoyed it, if you did make sure to leave a comment below.

No comments

please do not enter any spam link in the comment box.

Powered by Blogger.