# Issue categories

DeepSource detects issues using static analysis across categories like anti-patterns, bug risks, coverage, performance issues, security flaws, style issues and type check.

# Anti-patterns

Anti-patterns are certain ways of writing code that result in poor design. While anti-patterns are correct code, they are not recommended as they often affect maintainability, readability, performance, and security.

# Examples of anti-patterns

In Python, the file class is equipped with special methods that are automatically called whenever a file is opened via a with statement (e.g. with open("file.txt", "r") as file). These special methods ensure that the file is properly and safely opened and closed.

The code below does not use with to open a file. This code depends on you remembering to manually close the file via close() when finished. Even if you remember to call close(), the code is still dangerous, because if an exception occurs before the call to close() then close() will not be called and the memory issues can occur, or the file can be corrupted.

f = open('/tmp/.deepsource.toml', 'r')
f.write("config file.")
f.close()

Best practice will be to use with to open a file. The file class has some special built-in methods called __enter__() and __exit__() which are automatically called when the file is opened and closed, respectively. Therefore, Python guarantees that these special methods are always called, even if an exception occurs.

with open("/tmp/.deepsource.toml", "r") as f:
    f.write("config file.")

# Bug risks

Bug risks are issues in code that can cause errors in code and breakages in production. A bug is a flaw in the code that produces undesired or incorrect results.

Code often has bug risks due to poor coding practices, lack of version control, miscommunication of requirements, unrealistic time schedule for development and buggy third-party tools.

# Examples of bug risks

Passing mutable lists or dictionaries as default arguments to a function can have unforeseen consequences. Usually, when you use a list or dictionary as the default argument to a function, you want the program to create a new list or dictionary every time that the function is called. However, this is not what Python does. The first time that the function is called, Python creates a persistent object for the list or dictionary. Every subsequent time the function is called, Python uses that same persistent object created during the first call to the function.

In the code below the append function is used under the assumption that a new list with the object passed as the first argument would be returned each time that the function is called without the second argument. In reality, this is not what happens. The first time that the function is called, Python creates a persistent list. Every subsequent call to append, appends the value to that persistent list.

def append(number, number_list=[]):
    number_list.append(number)
    print(number_list)
    return number_list

append(5) # expecting: [5], actual: [5]
append(7) # expecting: [7], actual: [5, 7]
append(2) # expecting: [2], actual: [5, 7, 2]

The best practice is to use a sentinel value to denote an empty list, set or dictionary. This means if you want the function to return a singleton list each time this function is called without the second argument, then you can use a sentinel value to represent this use case, and then modify the body of the function to support this scenario.

# the keyword None is the sentinel value representing empty list
def append(number, number_list=None):
    if number_list is None:
        number_list = []
    number_list.append(number)
    print(number_list)
    return number_list

append(5) # expecting: [5], actual: [5]
append(7) # expecting: [7], actual: [7]
append(2) # expecting: [2], actual: [2]

# Performance issues

Performance issues are issues that impact the performance of the code on execution by slowing it down. Considerable performance gains can be obtained when appropriate functions and directives are used.

# Examples of performance issues

Using key in list to iterate through a list can potentially take n iterations to complete, where n is the number of items in the list. If possible, change the list to a set or dictionary instead, because Python can search for items in a set or dictionary by attempting to directly access them without iterations, which is much more efficient.

The code below defines a list 1 and then calls if 3 in 1 to check if the number 3 exists in the list. This is inefficient. Behind the scenes, Python iterates through the list until it finds the number or reaches the end of the list.

l = [1, 2, 3, 4]
# iterates over three elements in the list
if 3 in l:
    print("The number 3 is in the list.")
else:
    print("The number 3 is NOT in the list.")

In the modified code below, the list has been changed to a set. This is much more efficient behind the scenes, as Python can attempt to directly access the target number in the set, rather than iterate through every item in the list and compare every item to the target number.

s = set([1, 2, 3, 4])
if 3 in s:
    print("The number 3 is in the list.")
else:
    print("The number 3 is NOT in the list.")

# Security issues

A bug in the code which creates a potential risk of compromised security leads to security vulnerability issue. Using libraries and tools that are out-of-date or have known security issues can also introduce vulnerabilities in the system.

A highly dynamic language like Python that gives you many ways to change the runtime behavior of code and even dynamically execute new code is powerful but can be a security risk as well.

The three main types of security vulnerabilities based on their more extrinsic weaknesses are:

  • Porous defenses
  • Risky resource management
  • Insecure interaction between components

# Examples of security vulnerabilities

The exec statement enables you to dynamically execute arbitrary Python code which is stored in literal strings. Building a complex string of Python code and then passing that code to exec results in code that is hard to read and hard to test. Anytime the Use of exec error is encountered, you should go back to the code and check if there is a clearer, more direct way to accomplish the task.

The sample code below composes a literal string containing Python code and then passes that string to exec for execution. This is an indirect and confusing way to program in Python.

s = "print(\"Hello, World!\")"
exec s

In most scenarios, you can easily refactor the code to avoid the use of exec. In the example below, the use of exec has been removed and replaced by a function.

def print_hello_world():
    print("Hello, World!")

print_hello_world()

Assert statement is a debugging aid that tests a condition. If the condition is true, it does nothing and your program just continues to execute. But if the assert condition evaluates to false, it raises an AssertionError exception with an optional error message.

In the code below, assert statement is used in application logic which is discouraged.Asserts can be turned off globally in the Python interpreter. Don’t rely on assert expressions to be executed for data validation or data processing.

def delete_product(product_id, user):
    assert user.is_admin(), 'Must have admin privileges to delete'
    assert store.product_exists(product_id), 'Unknown product id'
    store.find_product(product_id).delete()

Assert statement should ideally be used only in tests. Instead, you could do your validation with regular if-statements and raise validation exceptions if necessary.

def delete_product(product_id, user):
    if not user.is_admin():
        raise AuthError('Must have admin privileges to delete')

    if not store.product_exists(product_id):
        raise ValueError('Unknown product id')

    store.find_product(product_id).delete()

# Style issues

Violations in the code format according to a style guide are style issues. If the code does not follow the code style guidelines then it fails to express its intent in the most readable way.

Code style can be boiled down to anything that is a stylistic choice in the code that has no effect on the behavior of the code

Any large code base with multiple team members should look as if only one programmer wrote it. If a team agrees on a given style it can help keep the code consistent.

# Examples of style issues

Per the PEP-8 Style Guide, all Python code should be consistently indented with 4 spaces, never tabs.

The following code mixes spaces and tabs for indentation. The print("Hello, World!") statement is indented with a tab. The print("Goodbye, World!") statement is indented with 4 spaces.

def print_hello_world():
	# indented with tab
	print("Hello, World!")

def print_goodbye_world():
    # indented with 4 spaces
    print("Goodbye, World!")

All Python code should be consistently indented with 4 spaces as in the code below.

def print_hello_world():
    print("Hello, World!") # indented with 4 spaces

def print_goodbye_world():
    print("Goodbye, World!") # indented with 4 spaces

In the python code there should be whitespace after the characters , , ; , and : . For instance in the code below there is a missing whitespace after ,.

class BaseNumberGenerator:
   def __init__(self):
      self.limits = (1,10)

   def get_number(self, min_max):
      raise NotImplemented

# Documentation issues

Documentation issues are caused due to the lapses in documentation of the source code. Documentation of source code is the collection of easy to understand images and written descriptions that explain what a code base does and how it can be used.

Documentation is important because it ensures that when you need to dive back in the code next, you won't have to take as much time to get up to speed.

# Example of documentation issues

PEP-8 mandates that all public modules, classes, functions, and methods should have a documentation string. A documentation string is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the doc special attribute of that object.

Ensuring that every public module, class, function, and method is documented makes it easier for other developers to maintain the code.

If a module, class, function, or method needs to be public then add a documentation string that describes the purpose or use of the object (see PEP-257 for guidelines). If the object does not need to be public then make it "private" by changing its name from xxx to _xxx.

The following simple, public function should be updated to include a documentation string immediately after the def line.

def add(x, y):
    return x + y

You might insert the documentation string: """Return the sum of x and y.""" on line 2 as shown in the code below.

def add(x, y):
    """Return the sum of x and y."""
    return x + y
Last Updated: 10/17/2020, 1:48:35 PM