Flag Hard-Coded Values¶
When your "one-off" script does its job really well, a logical next step is to reuse it for new purposes. :-) Hard-coded values tend to be the biggest hindrance in this endeavour. Especially if they are scattered accross the code and pop up at the most surprising places. Some rules can help you to detect and parametrize them.
Hard-Coded Values With A Specific Name¶
Let's say your script manipulates files. In the initial version the file names were hard-coded, but now you want to make it more flexible. A Sourcery custom rule to detect the hard-coded file names:
- id: no-hard-coded-file-name
description: Hard-coded file name
pattern: ${var} = ${value}
condition: |
(var.ends_with("file")
or var.ends_with("file_name")
or var.ends_with("dir")
or var.ends_with("dir_name"))
and value.is_str_literal()
explanation: |
File and directory names should be read from a parameter or the config.
tests:
- match: input_file = "test.txt"
- match: file_name = "placeholder.py"
- match: working_dir = "/home/user/test"
- no-match: other_file = input_file
- no-match: minutes_file = f"tt_entry_{colour}.txt"
- no-match: working_dir = Path()
To quickly see all the code this rule flagged, run the command:
sourcery review --enable no-hard-coded-file-name .
Hard-Coded Values In __init__
¶
A frequent anti-pattern is setting hard-coded values in the __init__
method.
Usually, these can be replaced with a parameter that has a default value. Here's
a rule flagging all string and literal hard-coded values in the __init__
methods:
- id: no-hard-coded-value-in-init
description: Don't set hard-coded values in `__init__`
explanation: Consider defining a parameter with a default value
pattern: |
def __init__(...):
...
${var} = ${value}
...
condition: |
value.is_str_literal()
or value.is_numeric_literal()
tests:
- match: |
def __init__(self):
nr = 42
- match: |
def __init__(self):
self.nr = 42
- match: |
def __init__(self):
some_init()
self.nr = 42
other_stuff(self)
- match: |
def __init__(self):
self.description = "a special object"
- no-match: |
def other_func(self):
nr = 42
What if you have a case where you really want to set a constant in the
__init__
method? That's fair, this rule is a guideline, not law. Feel free to
add an exception with a # sourcery skip
comment:
# sourcery skip: no-hard-coded-value-in-init
Where and which hard-coded values pop up, depends a lot on your code. Use the examples above as a starting point to create your own rules and make your code more re-usable and flexible.