Preface

A software architecture that is light and simple is very important to allow us to meet business requirements at minimal cost. So how to ensure light and simple? Then today we share with you the secrets of this, that is, 3 important guiding principles, KISS principle, YAGNI principle and DRY principle, you all know and understand?

KISS principle

The KISS principle, or Keep it simple and stupid, is the core idea of keeping things as simple as possible.

The KISS principle guides us to keep the software design as simple as possible, using some mature, business-friendly technical solutions. In addition, from a user's point of view, you design to think about how to make their own architecture design becomes simple enough to use, such as the framework you develop is not for access to low cost or even 0 cost? Do you design the framework does not invade the business code?

Not only the software architecture design level, but also at the code level to reflect the KISS principle everywhere. Code readability and maintainability are two very important criteria for measuring code quality. The KISS principle is an important tool to keep the code readable and maintainable. The code is simple enough, which means it is easy to read and understand, and bugs are harder to hide. Even if a bug appears, it is easier to fix.

Let's see which of the following 3 ways to verify whether the IP is legitimate is the most "KISS", which one do you think?

1. Mode 1

// The first way of implementation: using regular expressions
public boolean isValidIpAddressV1(String ipAddress) {
  if (StringUtils.isBlank (ipAddress)) return false;
  String regex = "^ (1\\d{2} |2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
    + "(1\\d{2]|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)l|."
    + " (1\ \ d{2}|2[0-4]\\d|25[0-5]|[1-97\\d||\d)ll."
    + " (1\\ d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
  return ipAddress.matches (regex);
}

2. Mode 2

//Second way of implementation: using ready-made tool classes
public boolean isvalidIpAddressv2 (String ipAddress){
  if (StringUtils.isBlank(ipAddress)) return false;
  String[] ipUnits = StringUtils.split(ipAddress, '. ');
  if (ipunits.length != 4){
  return false;
  }
  for (int i = o; i < 4; ++i) {
    int ipUnitIntValue;
    try {
      ipUnitIntValue = Integer.parseInt (ipUnits [i]);
    } catch (NumberFormatException e) {
      return false;
    }
    if (ipUnitIntValue < 0 || ipUnitIntValue > 255) {
      return false;
    }
    if (i == 0 && ipUnitIntValue == 0) {
      return false;
    }
  }
  return true;
}

3. Mode 3

//The third way of implementation: without using any tool classes
public boolean isValidIpAddressV3(String ipAddress) {
  char [] ipChars = ipAddress. toCharArray () ;
  int length = ipChars.length;
  int ipUnitIntValue = -1;
  boolean isFirstUnit = true;
  int unitsCount = 0;
  for (int i = 0; i ‹ length; ++i) {
    char c = ipChars[i];
    if (c == ' .') {
    if (ipUnitIntValue < 0 || ipUnitIntValue > 255) return false; if (isFirstUnit && ipUnitIntValue == 0) return false;
    if (isFirstUnit) isFirstUnit = false;
    ipUnitIntValue = -1;
    unitsCount++
    continue;

    if (c < '0' || c > '9') {
      return false;
    }
    if (ipUnitIntValue == -1) ipUnitIntValue = 0;
    ipUnitIntValue = ipUnitIntValue * 10 + (c - '0');
  }
  if (ipUnitIntValue < 0 || ipUnitIntValue > 255return falseif (unitsCount !=3) return false;
  return false;
}

  • The regular expression itself is complex, and writing a completely bug-free regular expression is challenging; on the other hand, not every programmer is proficient in regular expressions. For colleagues who do not know much about regular expressions, it is difficult to read and maintain the regular expressions. This implementation will lead to poor readability and maintainability of the code, so this implementation is not in line with the KISS principle in terms of the original design intention.

  • The second way uses some ready-made tool functions provided by StringUtils class and Integer class to handle IP address strings, which is logically clear and readable.

  • The third way does not use any tool function, but processes the characters in the IP address one by one to determine whether it is legal or not, which is prone to bugs and not easy to understand.

Summary: On balance, the second approach is more in line with the KISS principle. Not the less code the better, but also consider whether the code is logical and clear, easy to understand, and stable enough.

So how do you write code that satisfies the KISS principle?

  1. Do not use techniques to implement code that your colleagues may not understand. For example, regular expressions in the previous example, and syntax that is too advanced in some programming languages.

  2. Don't build the wheel over and over again, and be good at using the tool libraries that are already available. Experience has shown that if you implement these libraries yourself, the probability of bugs is higher and the cost of maintenance is higher.

  3. Don't over-optimize. Don't over-optimize your code by using tricks (e.g., bitwise operations instead of arithmetic operations, complex conditional statements instead of if-else, using functions that are too low-level, etc.) at the expense of code readability.

YAGNI Principle

The YAGNI principle in English is: You Ain't Gonna Need It, which means you don't need to do it at all. The core idea is not to over-design.

So what is overdesign? For example, your application is still at the stage of a single application, then this time I will consider ah, distributed transactions how to do, that or your database is still a single library, I am thinking. How to do data heterogeneity, or even your business logic in the not-so-complex stage, you will then think about how to go on a workflow engine, or rules engine. Then, for example, now the graphical database is very hot, then you are thinking of ways to introduce the graphical database into your own projects. So what is this? To put it nicely, it's called resume oriented programming, but to put it bluntly, what is it? Take off your pants and fart, it's a lot of work. What do we mean by advocating future-oriented architecture? Is to plan your future technology development route, when the day needs to come, you have the ability to achieve it. Not that you do not need the future now to move things into their own projects.

So never for the sake of technology and technology, must let technology to serve the business, follow the YAGNI principle, excessive design is a disaster.

DRY Principle

DRY principle, the full English name Don't Repeat Yourself, directly translated is not to repeat yourself. What do you mean by repeat here? Does it mean that the code is exactly the same to be considered duplication? No, I understand the meaning of repetition from three points: repetition of implementation logic, repetition of functional semantics and repetition of code execution.

1. Implementing logical repetition

Let's look at the following example:

  • Verify username

private boolean isValidUsername (String username) {
  // check not null, not empty
  if (StringUtils.isBlank (username)) {
  return false;
  }
  // check length: 4~64
  int length = username. length ();
  if (length < 4 || length > 64) {
    return false;
  }
  // contains only Lowcase characters
  if (!StringUtils.isAllLowerCase(username)) {
     return false;
  }
  // contains only a~z, 0~9, dot
  for (int i = 0; i < length; ++i) {
    char c = username.charAt (i);
    if (1(c>=1a188c<=1211(c>='08&c<=911c==
      return false;
    }
  }
  return true;
}

  • Verify Password

private boolean isValidPassword (String password) {
  // check not null, not empty 
  if (StringUtils.isBlank(password)) {
    return false;
  }
  //check length: 4~64
  int length = password.length();
  if (length < 4 || Length > 64) {
    return false;
  }
  // contains only lowcase characters 
  if (!StringUtils.isAllLowerCase(password)) {
    return false;
  }
  // contains only a~z, 0-9, dot 
  for (int i = 0; i < length; ++i) {
    char c = password.charAt (i);
    if (!(c >= 'a' && c <= 'z')||(c >= '0' && c <= '9')|| c=='.'){
      return false;
    }
  }
  return true;
}

Although the code logic of the above two methods is duplicated, but their semantics are not duplicated, one is used to verify the username, one is used to verify the password, so it is in line with the DRY principle. If we force them to encapsulate into isValidUsernameOrPassword() method, it is not in line with the single responsibility principle, what if the logic of password verification changes one day? So it is not that the code is the same must violate the DRY principle. On the contrary, sometimes the code is not the same, it happens to violate the DRY principle.

2. Functional semantic repetition

Directly on the example, different colleagues in the project because they do not know it wrote two methods of checking the IP.

  • Code I

public boolean isValidIp(String ipAddress){  
  if (StringUtils.isBlank(ipAddress)) return false;
  String regex = + "^(1\\d{2|2[0-4]\\d|25[0-5]|[1-9]\\d[1-9])\\."
"(1\\d{23|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
"(1\\d-27 2 0-47\\d|25[0-57|[1-97\\d|\\d)\\."
"(1\ d(27|2[0-4]\\d|25[0-5]|[1-97\\d|\(d)$";
  return ipAddress.matches (regex);
}

  • Code 2

public boolean checkIfIpValid(String ipAddress) {
  if (StringUtils.isBlank(ipAddress)) return false;
  String[] ipunits= stringutils.split (ipAddress,'.')
  if (ipUnits. length !=4){
    return false;
  }
  for (int i=0;i<4;++i){
    lint ipUnitIntValue;
    try {
      ipUnitIntValue = Integer.parseInt (ipUnits [i]);
catch (NumberFormatException e){
      return false;
    }
    if SipUnitIntValue < 0 || ipUnitIntValue › 255) {
      return false;
    }
    if (i == 0 && ipUnitIntValue == 0) {
      return false;
    }
  }
  return true;
}

Although the implementation logic of the two pieces of code is not duplicated, the semantic duplication, that is, the duplication of functions, we believe that it violates the DRY principle. We should unify one implementation idea in the project, and call the same function for all the places that are used to determine whether the IP address is legitimate or not. Otherwise, one day the verification rules change, it is easy to change only one of them, and the other one misses to change, there will be inexplicable bugs.

3. Code execution duplication

Let's look at the following code example for login.

public User login(String email, String password){
  boolean existed = userRepo.checkIfUserExisted (email, password);              
  if (!existed) 5
  // ...throw AuthenticationFailureException..        
  }
  User user = userRepo.getUserByEmail (email);
  return user;
}

Have you noticed that the email checksum logic is executed 2 times, and this repeated execution can be considered a violation of the DRY principle.

Having said that, what is a good way to improve code reusability and ensure the DRY principle?

  • Use ready-made wheels, do not easily build wheels

In fact, the most critical thing is to write code with brains, use a method first to see if there is a ready-made, do not look at not to see, there to do to build the wheel.

  • Reduce code coupling

For highly coupled code, when we want to reuse one of the functions, want to extract the code of this function to become a separate module, class or function, often find a hair to move the whole body. Move a little code, you have to involve a lot of other related code. Therefore, highly coupled code will affect the reusability of the code, we want to minimize code coupling.

  • Meet the single responsibility principle

As we said earlier, if the responsibility is not single enough, the module or class is designed to be large and comprehensive, the code that depends on it or the code it depends on will be more, which in turn increases the code coupling. According to the previous point, it will also affect the reusability of the code. On the contrary, the more fine-grained the code is, the better the generality of the code will be and the easier it will be to be reused.

  • Modularity

The term "module" here refers not only to a set of classes, but also to a single class or function. We need to be good at encapsulating functionally independent code into modules. Independent modules are like building blocks that are easier to reuse and can be used directly to build more complex systems.

  • Separate business and non-business logic

The more business-independent code is easier to reuse, the more business-specific code is harder to reuse. Therefore, in order to reuse business-independent code, we separate business and non-business logic code and extract them into some general frameworks, libraries, components, etc.

  • Generic code sinking

From the perspective of layering, the more generic the code at the bottom, the more modules will be called, and the more reusable it should be designed. In general, after code layering, in order to avoid cross-calls that lead to confusing call relationships, we only allow upper-level code to call lower-level code and calls between the same level of code, and never allow lower-level code to call upper-level code. Therefore, the generic code we try to sink to lower layers.

  • Inheritance, Polymorphism, Abstraction, Encapsulation

When talking about object-oriented features, we talked about how, using inheritance, we can extract public code to the parent class, and the child class reuses the properties and methods of the parent class. Using polymorphism, we can dynamically replace part of the logic of a piece of code to make this code reusable. In addition, abstraction and encapsulation, when understood in a broader sense rather than in the narrower sense of object-oriented features, are more abstract and less dependent on concrete implementation, the easier it is to reuse. The more code is encapsulated into modules, hiding variable details and exposing invariant interfaces, the easier it is to reuse.

  • Applying design patterns such as templates

Some design patterns, too, can improve code reusability. For example, the template pattern makes use of polymorphism to implement it, allowing flexible replacement of parts of the code and reusability of the entire process template code.

Summary

In this article and you are introduced to the field of software engineering to keep the three principles of simple architecture design, in fact, all these principles is to achieve a very simple purpose, try everything to keep it simple, software design on the simple light is very important.