Skip to content

Commit 3f0c960

Browse files
committed
first pass at slicing up docs into separate pages. placing index page under version control
1 parent 314617b commit 3f0c960

File tree

6 files changed

+1029
-1
lines changed

6 files changed

+1029
-1
lines changed

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
*_bkup*
55
html/*
66
_docs/_build/*
7-
_docs/index.rst

_docs/index.rst

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
refactoring101
2+
==============
3+
4+
.. figure:: http://i2.kym-cdn.com/photos/images/original/000/572/090/77f.jpg
5+
:alt: Draw the rest of the owl
6+
7+
Draw the rest of the owl
8+
9+
Inspiration
10+
-----------
11+
12+
"`Complexity kills <http://ozzie.net/docs/dawn-of-a-new-day/>`__." ~
13+
*Ray Ozzie*
14+
15+
"The art of simplicity is a puzzle of complexity." ~ *Douglas
16+
Horton*
17+
18+
"...you're not refactoring; you're just `changing
19+
shit <http://hamletdarcy.blogspot.com/2009/06/forgotten-refactorings.html>`__."
20+
~ *Hamlet D'Arcy*
21+
22+
Overview
23+
--------
24+
25+
This repo contains code samples demonstrating how to transform a
26+
complex, linear script into a modular, easier-to-maintain package. The
27+
code is written as a reference for the *Python: Beyond the Basics* class
28+
at `NICAR 2014 <http://ire.org/conferences/nicar-2014/>`__, but can also
29+
work as a stand-alone tutorial.
30+
31+
The tutorial uses a small, `fake set of election
32+
results <https://docs.google.com/spreadsheet/pub?key=0AhhC0IWaObRqdGFkUW1kUmp2ZlZjUjdTYV9lNFJ5RHc&output=html>`__
33+
for demonstration purposes.
34+
35+
Project code evolves through four phases, each contained in a numbered
36+
*elex* directory. Below are descriptions of each phase, along with
37+
related questions and exercises that anticipate the next phase or set of
38+
skills.
39+
40+
The goal is to demonstrate how to use Python functions, modules,
41+
packages and classes to organize code more effectively. We also
42+
introduce unit testing as a strategy for writing programs that you can
43+
update with confidence. The overarching theme: ***As an application or
44+
program grows in size, writing readable code with tests can help tame
45+
complexity and keep you sane.***
46+
47+
Wondering how to use this tutorial or why the hell we called it
48+
*refactoring101*? The
49+
`FAQ <https://github.com/PythonJournos/refactoring101/wiki/FAQ>`__ has
50+
answers to these and sundry other questions. Also, check out the
51+
`Resources <https://github.com/PythonJournos/refactoring101/wiki/Resources>`__
52+
page for wisdom from our tribal elders.
53+
54+
55+
TODO
56+
----
57+
58+
- Convert wiki pages to rst
59+
- Add tree view of *elex4* directory; doesn't look so different from
60+
*elex3* tree view (mainly added models.py and test\_models.py), but
61+
the underlying implementation has changed dramatically (hopefully for
62+
the better)
63+
- What's Next?
64+
- Refactoring book spells out more precise steps for refactoring.
65+
- So you have a solid test suite, and want to change some code. How do
66+
you ensure all references to that code have been updated?
67+
68+
- What didn't we cover?
69+
- *super* (for calling methods on parent classes)
70+
- Multiple inheritance and method resolution order (see the Diamond
71+
problem)
72+
- Decorators
73+
- Descriptors
74+
- Meta-programming and \_\_new\_\_ constructor
75+
76+
77+
.. toctree::
78+
:maxdepth: 2
79+
80+
phase1
81+
phase2
82+
phase3
83+
phase4

_docs/phase1.rst

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Phase 1 - Spaghetti
2+
-------------------
3+
4+
We begin with a single, linear script in the *elex1/* directory. Below
5+
are a few reasons why this `code
6+
smells <http://en.wikipedia.org/wiki/Code_smell>`__ (some might say it
7+
reeks):
8+
9+
- It's hard to understand. You have to read the entire script before
10+
getting a full sense of what it does.
11+
- It's hard to debug when something goes wrong.
12+
- It's pretty much impossible to test, beyond eye-balling the output
13+
file.
14+
- None of the code is reusable by other programs.
15+
16+
Questions
17+
^^^^^^^^^
18+
19+
- What are `unit
20+
tests <http://docs.python.org/2/library/unittest.html>`__?
21+
- Can you identify three sections of logic that could be unit tested?
22+
- What are
23+
`modules <http://docs.python.org/2/tutorial/modules.html>`__?
24+
- What are
25+
`packages <http://docs.python.org/2/tutorial/modules.html#packages>`__?
26+
27+
Exercises
28+
^^^^^^^^^
29+
30+
- Slice up this code into a bunch of functions, where related bits of
31+
logic are grouped together.
32+
- Write a unit test for one or more functions extracted from this
33+
module.

_docs/phase2.rst

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
Phase 2 - Function Breakdown
2+
----------------------------
3+
4+
In the *elex2/* directory, we've chopped up the original
5+
election\_results.py code into a bunch of functions and turned this code
6+
directory into a package by adding an \*\_\_init\_\_.py\* file.
7+
8+
We've also added a suite of tests. This way we can methodically change
9+
the underlying code in later phases, while having greater confidence
10+
that we haven't corrupted our summary numbers.
11+
12+
We can't stress this step enough: Testing existing code is *the*
13+
critical first step in refactoring. If the code doesn't have tests,
14+
write some, at least for the most important bits of logic. Otherwise
15+
you're just `changing
16+
shit <http://hamletdarcy.blogspot.com/2009/06/forgotten-refactorings.html>`__
17+
(see Ray Ozzie).
18+
19+
Fortunately, our code has a suite of `unit
20+
tests <http://docs.python.org/2/library/unittest.html>`__ for name
21+
parsing and, most importantly, the summary logic.
22+
23+
Python has built-in facilities for running tests, but they're a little
24+
raw for our taste. We'll use the
25+
`nose <https://nose.readthedocs.org/en/latest/index.html>`__ library to
26+
more easily run our tests:
27+
28+
.. code:: bash
29+
30+
nosetests -v tests/test_parser.py
31+
# or run all tests in the tests/ directory
32+
nosetests -v tests/*.py
33+
34+
Observations
35+
^^^^^^^^^^^^
36+
37+
At a high level, this code is an improvement over *elex1/*, but it could
38+
still be much improved. We'll get to that in Phase 3, when we introduce
39+
`modules <http://docs.python.org/2/tutorial/modules.html>`__ and
40+
`packages <http://docs.python.org/2/tutorial/modules.html#packages>`__.
41+
42+
Questions
43+
^^^^^^^^^
44+
45+
- What is \*\_\_init\_\_.py\* and why do we use it?
46+
- In what order are test methods run?
47+
- What does the TestCase *setUp* method do?
48+
- What other TestCase methods are available?
49+
50+
Exercises
51+
^^^^^^^^^
52+
53+
- Install `nose <https://nose.readthedocs.org/en/latest/index.html>`__
54+
and run the tests. Try breaking a few tests and run them to see the
55+
results.
56+
- List three ways this code is better than the previous version; and
57+
three ways it could be improved.
58+
- Organize functions in *election\_results.py* into two or more new
59+
modules. (Hint: There is no right answer here. `Naming things is
60+
hard <http://martinfowler.com/bliki/TwoHardThings.html>`__; aim for
61+
directory and file names that are short but meaningful to a normal
62+
human).

_docs/phase3.rst

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
Phase 3 - Modularize
2+
--------------------
3+
4+
In this third phase, we chop up our original *election\_results.py*
5+
module into a legitimate Python package. The new directory structure is
6+
(hopefully) self-explanatory:
7+
8+
::
9+
10+
├── elex3
11+
│   ├── __init__.py
12+
│   ├── lib
13+
│   │   ├── __init__.py
14+
│   │   ├── parser.py
15+
│   │   ├── scraper.py
16+
│   │   └── summary.py
17+
│   ├── scripts
18+
│   │   └── save_summary_to_csv.py
19+
│   └── tests
20+
│   ├── __init__.py
21+
│   ├── sample_results.csv
22+
│   ├── sample_results_parsed.json
23+
│   ├── sample_results_parsed_tie_race.json
24+
│   ├── test_parser.py
25+
│   └── test_summary.py
26+
27+
- ***lib/*** contains re-usable bits of code.
28+
- ***scripts/*** contains...well..scripts that leverage our re-usable
29+
code.
30+
- ***tests/*** contains tests for re-usable bits of code and related
31+
fixtures.
32+
33+
Note that we did not change any of our functions. Mostly we just
34+
re-organized them into new modules, with the goal of grouping related
35+
bits of logic in common-sense locations. We also updated imports and
36+
"namespaced" them of our own re-usable code under *elex3.lib*.
37+
38+
Here's where we start seeing the benefits of the tests we wrote in the
39+
*elex2* phase. While we've heavily re-organized our underlying code
40+
structure, we can run the same tests (with a few minor updates to
41+
*import* statements) to ensure that we haven't broken anything.
42+
43+
**Note**: You must add the *refactoring101* directory to your
44+
*PYTHONPATH* before any of the tests or script will work.
45+
46+
.. code:: bash
47+
48+
$ cd /path/to/refactoring101
49+
$ export PYTHONPATH=`pwd`:$PYTHONPATH
50+
51+
$ nosetests -v elex3/tests/*.py
52+
$ python elex3/scripts/save_summary_to_csv.py
53+
54+
Check out the results of the *save\_summary\_to\_csv.py* command. The
55+
new *summary\_results.csv* should be stored *inside* the *elex3*
56+
directory, and should match the results file produced by
57+
*elex2/election\_results.py*.
58+
59+
Questions
60+
^^^^^^^^^
61+
62+
- Do you *like* the package structure and module names? How would you
63+
organize or name things differently?
64+
- Why is it necessary to add the *refactoring101/* directory to your
65+
PYTHONPATH?
66+
- What are three ways to add a library to the PYTHONPATH?
67+
- What is a class? What is a method?
68+
- What is an object in Python? What is an instance?
69+
- What is the **init** method on a class used for?
70+
- What is *self* and how does it relate to class instances?
71+
72+
Exercises
73+
^^^^^^^^^
74+
75+
- Look at the original results data, and model out some classes and
76+
methods to reflect "real world" entities in the realm of elections.
77+
- Examine functions in *lib/* and try assigning three functions to one
78+
of your new classes.
79+
- Try extracting logic from the *summarize* function and re-implement
80+
it as a method on one of your classes.

0 commit comments

Comments
 (0)