
The big dreaded spaghetti monster by St. Murse
Software quality is an interesting topic to me. It is what is often missing in many projects that transform them into bloated unmaintainable big spaghetti monsters. Every programmer with some kind of experience knows what it is to work on those software. If you do not, it is unpleasant to say the least.
I have been reading different articles and blogs about my new favorite language, Python. Many of them come to the conclusion that Python is a weaker language because it does not report compilation errors like many other languages do. Accordingly, it makes Python a less suitable candidate to build quality software.
First, let me say that it is possible to build high quality software with Python. This also applies to other similar programming languages supporting a fully dynamic type system. It is true that Python will not return type related errors when compiled. The reason is that Python code is mainly interpreted and does not enforce a static type system. It is possible to compile Python code, but you will likely end up with byte code that will have to be interpreted later on (with a possible speed gain). For instance, let’s take the following code:
if __name__ == "__main__": z = 2 + "22" z.addtwelve() print z
You can compile that code by placing it in a file called add.py with the following statements:
import py_compile py_compile.compile("add.py")
With CPython, which is a common python implementation, you would get a file named add.pyc in the same directory that would contain a byte code representation of the add.py file. The compilation would not report any error. In any case, trying to run either add.py or add.pyc would result in the following error:
Traceback (most recent call last):
File "add.py", line 4, in
z = 2 + "22"
TypeError: unsupported operand type(s) for +: 'int' and 'str'
This example demonstrates that Python does enforce a strong type system but you have to actually execute your code to find out about type errors.
Type errors are one kind of error that are usually found at compile time in other languages like Java, C# or C++ that are not found by Python at compile time. There are also missing import errors, undefined classes, undefined methods, undefined functions, undefined operations, undefined variables and many others. In order to build a quality software, you should be able to find out about those errors before using it.
With Python, the solution to this problem seems too simple to be just that. You have to run your code and you have to run every statements. Not only will you find out about those errors, but you will also find many runtime errors.
Running your whole application before using it
A great deal of software quality is made possible by using different tools. I will try to present some of those tools that I wish would always be part of every new software projects.
A good way to run your code is to write unit tests for it. Running those tests will make you run your application without having to actually use it which does simplify the task of running your code. If you do not know about unit testing, I strongly suggest that you have a look around the web to find out what it is and how it can help you. You can also have a look at an introduction I wrote a few weeks ago.
Another question that will probably be raised in your mind is how it is possible to know that you have ran your whole application or every statements in it. This is made possible by code coverage tools.
Code coverage tools are able to analyze how much of your code and which parts has been executed by a defined execution. While is it possible to use a code coverage tool on a single execution or multiple ones of our application without using unit tests, it makes more sense to use it with your tests. As you are creating your unit tests, you will want to run 100% of your code with them. Knowing that 100% of your code can be executed without errors will give you a good foundation for quality assurance.
I will show you an example with Python using nose, a unittest extension and the coverage module. Let’s say that I have a module named mymodule.py which contains:
class Counter(): def __init__(self): self.x = 1 def add(self): self.x = self.x + 1 def show(self): print self.x
Let’s say that I have a test file named tests.py which contains:
def test(): import mymodule c = mymodule.Counter() c.add()
I could run the following line to get a coverage report of my module with my test function:
nosetests --with-coverage tests.py
The results would be:
. Name Stmts Exec Cover Missing ---------------------------------------- mymodule 7 6 85% 9 ---------------------------------------------------------------------- Ran 1 test in 0.010s OK
If I change my tests.py file to this:
def test(): import mymodule c = mymodule.Counter() c.add() c.show()
And I run again the same command line, I would get:
. Name Stmts Exec Cover Missing ---------------------------------------- mymodule 7 7 100% ---------------------------------------------------------------------- Ran 1 test in 0.009s OK
If I rename my show method in the mymodule.py module to display and run the test again, I would get the following:
E
======================================================================
ERROR: tests.test
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python2.5/site-packages/nose-0.10.4-py2.5.egg/nose/case.py", line 182, in runTest
self.test(*self.arg)
File "/home/remy/projects/test-python/tests.py", line 7, in test
c.show()
AttributeError: Counter instance has no attribute 'show'
Name Stmts Exec Cover Missing
----------------------------------------
mymodule 7 6 85% 9
----------------------------------------------------------------------
Ran 1 test in 0.014s
FAILED (errors=1)
Using unit tests and code coverage, you can build high quality software with Python too. To be fair, most commonly used languages have their own tools too to create, run unit tests and get a code coverage report. If they do not, you can probably craft your own tools for this purpose.
Quality often requires efforts but sometimes all you have to do is integrate a few tools to get a good start at the job.
Comments 3
I think it is incorrect to say that python ‘enforces a strong type system’. All that the sample showed was that int doesn’t know how to add itself to a string, nor that a string knows how to add itself to an int.
eg, if we make a subclass of the string class (str) and try that example again:
>>> class String(str):
….: def __radd__(self, other):
….: return String(self + str(other))
….:
>>> 22 + String(’22′)
‘2222′
Type system gone
Posted 23 Oct 2008 at 12:17 am ¶Hi ale,
No it *is* right to say that Python is strongly typed. Strong typing just means that the language doesn’t try and guess a type conversion for you between dissimilar types. One can, as you have shown, explicitly make conversions.
A weakly typed language would allow you to add the string “1″ to the number 1 and return 2 for example.
What maybe different from what you are used to, is that types in Python are associated with values not with the names (variables) that can be associated with those values.
- Paddy.
Posted 23 Oct 2008 at 1:46 pm ¶My personal understanding is like what Paddy3118 said. Denying implicit conversion is the key point. I might be wrong though. Adding implicit conversion like you did for str might change it, but I am no expert.
Posted 23 Oct 2008 at 9:55 pm ¶Post a Comment