Clean Code and Refactoring
About us

Sven Marcus

Dennis Gläser

Sören Peters

Agenda
  • Motivation
  • Names / Comments / Functions
  • Hands-on coding exercise
Motivation
  • 84% of scientists say that developing software is essential to their research. [1]
  • Incorrect publications have led to a reproducibility and credibility crisis. [2, 3]

[1] Jo Hannay et al. “How Do Scientists Develop and Use Scientific Software?”
[2] Monya Baker. “1,500 scientists lift the lid on reproducibility”.
[3] Zeeya Merali. “Computational science: Error, why scientific programming does not compute”.

Researchers...

  • often lack knowledge about principles and practices of the software engineering discipline. [3, 4]
  • do not gain reputation for developing software
  • are pressed to publish results as fast as possible [5]

[4] Lucas Joppa et al. “Troubling Trends in Scientific Software Use”.
[5] Mark De Rond and Alan N Miller. “Publish or perish: bane or boon of academic life?”

Consequences

  • Low code quality
  • Hard to understand
  • (maybe) neither published nor documented

What is Clean Code?

  • Term coined by Robert C. Martin
  • No strict rules, but a set of principles to make code easy to understand, extend and adapt

“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. ...[Therefore,] making it easy to read makes it easier to write.” - Robert C. Martin

Clean Code

“Clean code always looks like it was written by someone who cares.”
- Michael Feathers

“Clean code reads like well-written prose.”
- Grady Booch

“Clean code can be read, and enhanced by a developer other than its original author.”
- Dave Thomas

Names / Comments / Functions
Names


                            var = 7  # num days in week
                        
Names


                            number_of_days_in_week = 7
                        
Names


                            def copy_chars(a1, a2):
                                for i in range(0, len(a1)):
                                    a2[i] = a1[i]
                        
Names


                            def copy_chars(source, destination):
                                for i in range(0, len(source)):
                                    destination[i] = source[i]
                        
Names


                            def genymdhms():  # generate date, year, month, day, minutes
                        

Names


                            def generate_timestamp():
                        
  • Use names that are pronounceable and searchable
  • Avoid encodings (unless they are commonly known)
Names


                            def do_the_next_thing():
                        
Names


                            def apply_neumann_boundary_condition():
                        

Use domain names!

Comments

“Don't comment bad code - rewrite it.”
- Brian W. Kernighan and P.J. Plaugher

“The proper use of comments is to compensate for our failure to express ourself in code. Note that I used the word failure. I meant it. Comments are always failures.”
- Robert C. Martin

Comments

If a comment must be used, it should describe the why and not the what or how

Comments


                            # check if i is a prime number
                            prime = True
                            for j in range(i):
                                if i % j == 0:
                                    prime = False
                                    break
                        
Comments


                            def is_prime(number: int) -> bool:
                                for divisor in range(number):
                                    if number % divisor == 0:
                                        return False
                                return True
                        

Express your intent in code instead of comments

Comments


                            class Container:
                                def __init__(self, logger) -> None:
                                    # The logger associated with this container
                                    self.logger = logger
                        
Comments

🤔


                            def load_config(parser: ConfigParser, config_path: str) -> list[str]:
                                """
                                Loads configuration from a config file.
                                Falls back to the 'defaults' file on fail.
                                """
                                config = parser.read(config_path)
                                if not config:
                                    config = parser.read(".defaults")
                                return config
                        
Comments

It's a lie! 😱


                            def load_config(parser: ConfigParser, config_path: str) -> list[str]:
                                """
                                Loads configuration from a config file.
                                Falls back to the 'defaults' file on fail.
                                """
                                config = parser.read(config_path)
                                if not config:
                                    config = parser.read(".defaults")
                                return config
                        
Good comments

Explanation of intent


                            # We're doing this because...
                        

Warning


                            # This code could lead to a dead lock, couldn't fix it yet
                        

Amplification


                            # Cutting out spaces here is important because...
                        
Functions


                            a = [1, 2, 3, 4]
                            with open("log_a.txt", "w") as f:
                                for num in a:
                                    f.write(str(num) + "\n")

                            b = [1, 2, 3, 4]
                            with open("log_b.txt", "w") as f:
                                for num in b:
                                    f.write(str(num) + "\n")
                        
Functions


                            def write_list_to_file(
                              a_list: list[int], filename: str
                            ) -> None:
                                with open(filename, "w") as f:
                                    for num in a:
                                        f.write(str(num) + "\n")

                            write_list_to_file(a, "log_a.txt")
                            write_list_to_file(b, "log_b.txt")
                        

DRY - Don't repeat yourself!

Functions According to Robert C. Martin functions should only have a single task or responsibility. This is called the Single Responsibility Principle.
Functions Applying the Integration-Operation-Segregation-Principle makes it easier to conform to the SRP.
Functions The IOSP says an integration function can only call other functions, while operation functions can only perform low level operations.
Functions