Software Design Principles
https://www.slideshare.net/oriteshsingh/software-design-principles-21024718

Software Design Principles

In this article we will discuss Software Design Principles ... what are the principles that we should follow while we are building our software, those principles are there to help us make robust, maintainable, flexible, and scale-able software ... those principles will guide you through building your software

1- Divide and Conquer : Divide your system into small, reusable, high cohesive parts

Trying to deal with big bunch of code is surely harder than dealing with small chunks, Dividing the code in small chunks will give us some benefits :

  • The developers can be able to work on different parts in the system in parallel
  • Smaller chunks will be easier to understand, easier to test, and easier to re-use
  • Refactoring or changing a small chunk of code is way more easier and safer than doing so in a big class that is used in multiple parts of the system
  • The smaller your code unit becomes, the more cohesive it is, and this respects the "Single Responsibility Principle"
  • Functional programming is driven mainly by this design principle, and this can be done in Object Oriented Programming as well, for example :
// a big class that should be divided :

?class LocationUtils {

    // one responsibility is to check for location availability
    
    public boolean isGpsLocationEnabled(){
        // ...
    }

    public boolean isNetworkLocationEnabled(){
        // ...
    }

    // another responsibility is to get locations :
?
    public Location retrieveOneLocation(){
        // ...
    }

    public void retrieveMultipleLocations(Consumer<Location> listener){
        // ...
        listener.accept(newLocation);?
    }

}


// dividing the class into smaller classes based on there responsibility :

?class LocationAvailability {

    public boolean isGpsLocationEnabled(){
        // ...
    }

    public boolean isNetworkLocationEnabled(){
        // ...
    }

}

class LocationRetriever {

    public Location retrieveOneLocation(){
        // ...
    }

    public void retrieveMultipleLocations(Consumer<Location> listener){
        // listen on location
        listener.accept(newLocation);?
    }
}

// --------------------------------------------------------

// There is still more better way in dividing the class, since all the methods 
// are pure-functions (they do not update member variables or change the 
// state of the class that they are in) a better way is to 
// use functional programming style and ?divide each method 
// in a separate functional class that holds only one public method :

// --------------------------------------------------------?
?
class GpsLocationAvailabilityChecker implements Callable<Boolean> {
    @Override
    public Boolean call() {
        // check for Gps Location availability
    }
}

class NetworkLocationAvailabilityChecker implements Callable<Boolean> {
    @Override
    public Boolean call() {
        // check for Network Location availability
    }
}


class OneLocationRetriever implements Callable<Location> {
    @Override
    public Location call() {
        // poll one location
    }
}

class MultipleLocationsRetriever implements Consumer<Consumer<Location>> {
    @Override
    public void accept(Consumer<Location> locationListener) {
        // listen on location
        locationListener.accept(newLocation);?
    }
}

* Callable.java and Consumer.java are Java functional interfaces, notice that after breaking down our class into separate functions (functional classes), there is no limitation on moving or re-using any of the functions, if we want to move one function to another package, nothing will be affected, further more, since all the functions implement Java functional interfaces, the users of those functions do not need to know the implementer of the java interface, like in this function :

class MultipleLocationsRetriever implements Consumer<Consumer<Location>> {
    @Override
    public void accept(Consumer<Location> locationListener) {
        // listen on location
        locationListener.accept(newLocation);??
    }
}

the expected "locationListener" is a class of type the Consumer.java as well, so if the implementer of this interface changed or moved or even deleted, this class will not be affected ... more about this subject in the Coupling and Abstraction sections.

2- Cohesion : Increase Cohesion where possible

We can measure the organization of the software by it's cohesion, more on this subject in this article :

3- Coupling : Reduce Coupling where possible

A big software will hold many relations between it's components, and that's where coupling comes to play, tightly coupled system components makes it harder to re-use, maintain, and scale ... more about this subject in this link :

4- Abstraction : Keep the level of abstraction as high as possible

Make sure that your code makes it easy to hide as much details as possible, like looking at the following code snippet :

class MessangerView {

    private Function<String, Boolean> messageSender;

    public void onClickSendButton(String message) throws Exception {
        boolean messageSent = messageSender.apply(message);
        if (!messageSent) {
            // show error
        }
    }
}


class MessageSender implements Function<String, Boolean> {

    private Function<String, String> messageFormatter;
    private Function<String, Boolean> messageApi;

    @Override
    public Boolean apply(String message) throws Exception {
        String formattedMessage = messageFormatter.apply(message);
        return messageApi.apply(formattedMessage);
    }
}

class MessageFormatter implements Function<String, String> {
    @Override
    public String apply(String originalMessage) throws Exception {
        // format the originalMessage and return a formatted String
    }
}


class SendMessageApi implements Function<String, Boolean> {
    @Override
    public Boolean apply(String messageContent) throws Exception {
        // send the messageContent to server and return true
        // if sent, or false if failed
    }
}

As you can see, all the classes depend on Java interfaces, which makes it very easy to change the implementer of those interfaces at any point ... as long as we are dealing with abstractions and interfaces, the system will be very flexible

5- Re-usability : Increase re-usability where possible

Design your code so that it can be re-used in multiple contexts, you will need to follow the previous principles to be able to increase the re-usability of the code as well

Put in mind that re-usability comes at the cost of having more complexity, so you must manage the trade-off between making your code re-usable or making it simple ... but if you can make it re-usable in a simple way this will be the best to do

One way to gain re-usability and reduce complexity is to divide your code into small functions and implement the known functional interfaces where possible, like the code snippets in the Abstraction principle ... also notice that if your over all design is simple, this will help re-using it's inner components as well

6- Re-use Existing : Re-use existing design and code where possible

Building upon the previous principle, re-using the existing code and design benefits from the investments of the others, but put in mind that "Cloning" or "Copy/Paste" is NOT considered re-usability, you should never clone or copy/paste code, always respect the "DRY principle" (Don't Repeat Yourself)

7- Flexibility : Increase the flexibility of your system

Design your code to be prepared for future changes, we can achieve this through :

  • Reduce Coupling and increase Cohesion
  • Create and work with Abstractions
  • Never Hard-Code any thing
  • Leave all options open, do not put limitations that hinders modifying the system in the future ... like forcing developers to implement or extend certain classes even if they do not need there functionality - favor composition over inheritance
  • Use Re-usable code, and the new code re-usable as well

8- Anticipate Obsolescence / Deprecation : prepare for changes

The more you use external code, the more often you will get hit with deprecated parts and soon will need to change your code to deal with the new code

  • Avoid using early releases
  • Avoid using libraries that are specific for particular environments (like in Android for example, a library that targets a certain mobile vendor)
  • Avoid using undocumented libraries
  • Avoid using Software from companies that will not provide long-term support
  • Use standard languages and technologies that are supported by multiple vendors

"Uncle Bob" mentioned in his "Clean Architecture" that any external dependency should be outside the core of the application, they should be isolated from the business rules of the application

Also you can use some Coupling and Cohesion techniques to separate between your code and the external code, like putting the external code in a separate Layer, or putting interfaces between your code and the external code ... some techniques are mentioned in the links related to Coupling and Cohesion

9- Portability : Design your software to be portable

Always Design your code to be portable, avoid depending on a certain OS while you are building your software, in Android for example, avoid depending on code related to a specific Mobile Vendor

10- Test-ability : Design your code to be tested by another code

Unit Testing is one of the major reasons for software stability, if you have fast and efficient test suit, a suit that you trust with your life ... you can guarantee the stability of your software ... the more you divide your code, and deal with abstracts, the more easily you can unit test every class and supply it with fake Objects implementing the Abstract classes that it uses to do it's Job ... respecting the past rules will make unit testing your code easy to achieve

11- Design Defensively : Never trust how others will try to use your code

Design your code in a way that makes sure that no one will use it the wrong way, like for example, if your function is public, you should expect that some one may pass "null" to it, if it needs an object that should meet a certain criteria, you can make this function take a certain interface instead of any type, and so on

But put in mind that excessive defensive design may result in a very bad code, so you should make things as simple and clear as possible

* This article is based on the following videos :

  • https://youtu.be/FMKv8Vozf5c
  • https://youtu.be/XQnytAeZrWE


Khaled Mosaad

Senior Java Software Developer at Vodafone International Services (VIS)

7 年

Great article .. your way of explanation is so simple .. keep going on :)

Aamir Riaz

7+ Year as Senior Mobile Application Developer (Kotlin Multiplatform | Android Jetpack Compose | Flutter)

7 年

Nice article

Naeem ur Rahman

Director Of Information Technology at Time Solution Pvt Ltd.

7 年

??? ????? ???? ??? ???

Ahmed Elbarody

Android Engineer @ Adres ,Ex: MaxAB E-commerce I fintech l Android Jetpack

7 年

??? ???? ????? ???? ???? clean architecture

回复

要查看或添加评论,请登录

Ahmed Adel Ismail的更多文章

  • Sharing data across multiple Mobile Squads - with examples

    Sharing data across multiple Mobile Squads - with examples

    Earlier I shared an article suggesting a solution to a common problem with teams following the "Spotify Model", which…

  • SDD - Squad Driven Design

    SDD - Squad Driven Design

    Working in multiple big teams I've found that we are always trying to apply our known best practices in software, but…

    4 条评论
  • Easier Testing with MVVM, MVI, MVP and Kotlin Multiplatform

    Easier Testing with MVVM, MVI, MVP and Kotlin Multiplatform

    Before we start, this article requires basic knowledge about the following topics : Clean Architecture Unit Testing…

    10 条评论
  • Android - A Cleaner Clean Architecture

    Android - A Cleaner Clean Architecture

    It has been a while now since Clean Architecture was out, and even many of us started embracing hexagonal (ports and…

    10 条评论
  • Beyond Functional Programming

    Beyond Functional Programming

    In the Android industry, lately functional programming was the all new stuff to learn, RxJava, Kotlin, and the whole…

    7 条评论
  • Dependency Injection in Clean Architecture

    Dependency Injection in Clean Architecture

    After Google's Opinionated Guide to Dependency Injection Video, Google made a clear statement that they want developers…

    18 条评论
  • Meta Programming in Android

    Meta Programming in Android

    Year after year we are getting rid of the boilerplate code that we need to write for small and simple tasks in Android,…

    2 条评论
  • MVI Pattern For Android In 4 Steps

    MVI Pattern For Android In 4 Steps

    Lately I wrote an article about MVI pattern, but as we are facing new problems every day and face more use-cases, we…

    7 条评论
  • Agile - Moving Fast

    Agile - Moving Fast

    We always here about Agile, and think about which methodology do we use, what practices do we have, team velocity…

    1 条评论
  • Kotlin Unit Testing with Mockito

    Kotlin Unit Testing with Mockito

    I've always believed that, if the code is designed to be tested, we wont need any testing framework or library ..

    17 条评论

社区洞察

其他会员也浏览了