More technical articles and test materials can be found here

Original link: ceshiren.com/t/topic/104…

Single responsibility

Single Responsibility is the Sin SOLD, which is the English word for Single Responsibility Principle. For example, if a back-end programmer develops only the back end, he or she may have a single responsibility, but the back-end programmer does both the front end and the back end, and also maintains the server, so the programmer’s responsibilities are not single enough.

Single responsibility means that the responsibility of a class or module should be single. Classes and modules here are like large circles and small circles, and classes are small circles that represent fine-grained code such as functions, classes, even variable names, and so on. The responsibilities are not simple enough.

  • Large and complete function design: data reading function includes path resolution, directory lookup and data detection.
  • The method inside the class is many and miscellaneous: animal class includes cat walking method, cat running method, dog walking method… .
  • Ambiguous variable or function names: The variable or function names are all util, Commmon and other common names.

Modules are large circles that represent coarse-grained code, such as a file (containing multiple classes). The responsibilities are not simple enough.

  • The resource module contains classes that are independent of resources: for example, the Resource module contains log extraction classes.

As you can see, both large and small circles emphasize the same thing:Don’t design big, all-in-one (rough) code, design single-purpose (elaborate) codeAs long as the design is not specific, it can be said that the single responsibility principle is violated. However, in practical development, it is difficult to have a clear judgment standard for a single responsibility, such as cats, which have various methods about cats: trotting, running, eating dried fish, eating cat food, drinking water, drinking pure water, standing, lying and sleeping. If you include all the cat-related actions, the class becomes bloated. You might want to think about how to modify it to make the cat’s role more simple:

The more sensible approach is to break it up. Cats can be divided into three subgroups and divided into three subgroups: cat – action, cat – eating and drinking, and cat – stillness. The three classes are more monolithic, but does this split meet all the requirements?

Unfortunately, the split class does not meet all requirements. Pet hospitals only care about the state of health of cats, and they divide them into healthy and unhealthy categories. Relatively speaking, regular exercise is healthy, such as jogging, running, and eating cat food and drinking pure water is also healthy. The pet hospital’s understanding of a single responsibility looks like this:

Note: the above views about cats are pure fiction and have no reference value!

From the above, it can be concluded that the understanding of a single responsibility varies depending on the requirements. Is a single responsibility so abstract? Is there a one-size-fits-all method to help us identify single responsibilities? Unfortunately, they don’t, which is why design principles are easy to understand and difficult to implement. The same method is applicable in project A, and is transition optimization in project B. Here are some of your problems and solutions that you can use as references, but don’t copy.

When writing the test framework, you need to load the YAML file in the specified directory. Code details have been omitted for the sake of clarity (code is not executable) :


def load(path) :
    # Use the YAML read function to read yamL data from the stream
    data = yaml.load(path)
    # return data
    return data


Print the loaded content
load("tmp.yaml")

Copy the code

This code is as simple as using the YAML library to load the YAML passed in by the path path. As the number of calls increases, it is found that the path is passed each time. Now a new function is added: automatic traversal of the directory to find yamL files that meet the requirements.


def load(path, yaml_name="tmp.yaml") :

    if os.path.isdir(path):
        list = os.listdir(path)  # list all directories and files in this folder
        for i in range(0.len(list)) :# recursively traverse directory files to find yaml files with the same yaml_name
    else:
        # Use the YAML read function to read yamL data from the stream
        data = yaml.load(path)
    # return data
    return data


Print the loaded content. The following two ways are equivalent
load("c:/a/b/tmp/tmp.yaml")
load("c:/a"."tmp.yaml")

Copy the code

This code implements directory traversal, which finds all directories and files through os.listdir, and recurses until it encounters a YAML file that matches yaml_name. Since it is still inconvenient to pass path manually, we now require to add a new traversal method: If the environment variable BASE_DIR is set, then the path parameter in the load function is invalidating, and the load function starts to traverse the directory of the environment variable BASE_DIR, looking for yamL files that match yaml_name.


def load(path=None, yaml_name="") :
    if path is None:
        Find the directory from the environment variable
        path = os.getenv("BASE_DIR", default = "")
    if os.path.isdir(path):
        list = os.listdir(path)  # list all directories and files in this folder
        for i in range(0.len(list)) :# recursively traverse directory files to find yaml files with the same yaml_name
    else:
        # Use the YAML read function to read yamL data from the stream
        data = yaml.load(path)
    # return data
    return data


Print the loaded content. The following two ways are equivalent
load("c:/a/b/tmp/tmp.yaml")
load("c:/a"."tmp.yaml")
load(yaml_name="tmp.yaml")

Copy the code

The above code adds the environment variable fetch, so far it is relatively simple. As the requirements increase, the load function will become more and more complex. In the future, it may support regular matching, directory blacklisting, limiting the level of searching subdirectories… . The load function becomes large and complete, violating a single responsibility, and can be split:


def load_by_data(yaml_name) :
    # Use the YAML read function to read yamL data from the stream
    data = yaml.load(path)
    return data


def load_by_dir(path=None, yaml_name="") :
    if not os.path.isdir(path):
        raise ValueError()
    list = os.listdir(path)  # list all directories and files in this folder
    data = None
    for i in range(0.len(list)) :# recursively traverse directory files to find yaml files with the same yaml_name
        data = load_by_data(xxx)   
    return data

def load_by_env(yaml_name) :
    Find the directory from the environment variable
    path = os.getenv("BASE_DIR")
    # reuse load_by_dir
    return load_by_dir(path, yaml_name)


def load(path=None, yaml_name="") :
    if path is None:
        return load_by_env(yaml_name, default = "")
    elif os.path.isdir(path):
        return load_by_dir(path, yaml_name)
    else:
        return load_by_data(yaml_name)


Print the loaded content. The following two ways are equivalent
load("c:/a/b/tmp/tmp.yaml")
load("c:/a"."tmp.yaml")
load(yaml_name="tmp.yaml")
Copy the code

The load function is broken down into smaller functions that have a single function:

  • Load_by_data: search directly

  • Load_by_dir: lookup from the directory

  • Oad_by_env: Looks up from the environment variable

  • Oad: Combines the above functions

The above split is just an example, not an optimal solution. There are many ways to think about a single responsibility, but the most important is how the programmer weighs it, whether the design of the responsibility is reasonable, and whether the complexity of breaking it down or refactoring it is not worth the loss. As in the example above, if the load function needs to be simple, there is no need to split it.

This article is not a silver bullet, but if you think about responsibility when you write code, I’ve done my job. Measuring a single responsibility requires knowing the business well enough to find your own yardstick through constant thinking, dissatisfaction with the status quo, and constant refactoring.

conclusion

  • Single responsibility means that the responsibility of a class or module should be single. Note that classes and modules are like large circles and small circles. Classes are small circles, representing fine-grained code such as functions, classes, and even variable lives; Modules are large circles that represent coarse-grained code, such as a file (containing multiple classes).

  • Different requirements can be split in different ways. There is no one-size-fits-all method to help us judge A single responsibility. The same method is suitable for project A, and transitional optimization for project B.

  • Measuring a single responsibility requires knowing the business well enough to find your own yardstick through constant thinking, dissatisfaction with the status quo, and constant refactoring.

More technical articles and test materials can be found here