Skip to content

Commit 15df5a6

Browse files
committed
README fixes/updates
1 parent f6b0be5 commit 15df5a6

File tree

1 file changed

+78
-40
lines changed

1 file changed

+78
-40
lines changed

README.md

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,32 @@
22

33
## Overview
44

5-
This repo contains code samples demonstrating how to transform a complex, linear script into a modular, easier-to-maintain package. The code is written as a reference for the *Python: Beyond the Basics* class at [NICAR 2014](http://ire.org/conferences/nicar-2014/), but can also work as a stand-alone tutorial.
5+
This repo contains code samples demonstrating how to transform a complex, linear script into a modular,
6+
easier-to-maintain package. The code is written as a reference for the *Python: Beyond the Basics* class
7+
at [NICAR 2014][], but can also work as a stand-alone tutorial.
68

7-
The tutorial uses a small, [fake set of election results](https://docs.google.com/spreadsheet/pub?key=0AhhC0IWaObRqdGFkUW1kUmp2ZlZjUjdTYV9lNFJ5RHc&output=csv) created for demonstration purposes.
9+
The tutorial uses a small, [fake set of election results][] for demonstration purposes.
810

9-
Project code evolves through four phases, each contained in a numbered *elex* directory. Below are descriptions of each phase, along with related questions and exercises that often anticipate the next phase or set of skills.
11+
Project code evolves through four phases, each contained in a numbered *elex* directory. Below are descriptions of each phase,
12+
along with related questions and exercises that often anticipate the next phase or set of skills.
1013

11-
The goal is to demonstrate how to use Python functions, modules, packages and classes to organize code more effectively. We also introduce unit testing as a strategy for writing programs that you can update with confidence. The overarching theme: **_As an application or program grows in size, writing readable code with tests can help tame complexity and keep you sane._**
14+
The goal is to demonstrate how to use Python functions, modules, packages and classes to organize code more effectively.
15+
We also introduce unit testing as a strategy for writing programs that you can update with confidence. The overarching theme:
16+
**_As an application or program grows in size, writing readable code with tests can help tame complexity and keep you sane._**
1217

13-
Wondering how to use this tutorial or why the hell we called it *refactoring101*? The [FAQ](https://github.com/PythonJournos/refactoring101/wiki/FAQ) has answers to these and sundry other questions. Also, check out the [Resources](https://github.com/PythonJournos/refactoring101/wiki/Resources) page for wisdom from our tribal elders.
18+
Wondering how to use this tutorial or why the hell we called it *refactoring101*? The [FAQ][] has answers to
19+
these and sundry other questions. Also, check out the [Resources][] page for wisdom from our tribal elders.
20+
21+
[NICAR 2014]: http://ire.org/conferences/nicar-2014/
22+
[fake set of election results]: https://docs.google.com/spreadsheet/pub?key=0AhhC0IWaObRqdGFkUW1kUmp2ZlZjUjdTYV9lNFJ5RHc&output=html
23+
[FAQ]: https://github.com/PythonJournos/refactoring101/wiki/FAQ
24+
[Resources]: https://github.com/PythonJournos/refactoring101/wiki/Resources
1425

1526
## Phase 1 - Spaghetti
1627

17-
We begin with a single, linear script in the _elex1/_ directory. Below are a few reasons why this [code smells](http://en.wikipedia.org/wiki/Code_smell) (some might even say it reeks):
28+
We begin with a single, linear script in the _elex1/_ directory. Below are a few reasons why this [code smells][] (some might even say it reeks):
29+
30+
[code smells]: http://en.wikipedia.org/wiki/Code_smell
1831

1932
* It's hard to understand. You have to read the entire script before getting a full sense of what it does.
2033
* It's hard to debug when something goes wrong.
@@ -29,7 +42,7 @@ We begin with a single, linear script in the _elex1/_ directory. Below are a few
2942

3043
#### Exercises
3144

32-
* Try slicing up this code into a bunch of functions, where related bits of logic are grouped together.
45+
* Slice up this code into a bunch of functions, where related bits of logic are grouped together.
3346
* Write a unit test for one or more functions extracted from this module.
3447

3548

@@ -49,7 +62,10 @@ could be a lot better.
4962
#### Exercises
5063

5164
* List three ways this code is better than the previous version; and three ways it could be improved.
52-
* Organize functions in election_results.py into two or more new modules. (Hint: There is no right answer here. [Naming things is hard](http://martinfowler.com/bliki/TwoHardThings.html); aim for directory and file names that are short but meaningful to a normal human).
65+
* Organize functions in election_results.py into two or more new modules. (Hint: There is no right answer here.
66+
[Naming things is hard][]; aim for directory and file names that are short but meaningful to a normal human).
67+
68+
[Naming things is hard]: http://martinfowler.com/bliki/TwoHardThings.html
5369

5470
## Phase 3 - Modularize
5571

@@ -81,7 +97,7 @@ Note that we did not change any of our functions. Mostly we just re-organized th
8197
with the goal of grouping related bits of logic in common-sense locations. We also migrated
8298
imports and "namespaced" imports of our own re-usable code under _elex3.lib_.
8399

84-
**Important**: You must add the _refactoring101_ directory to your _PYTHONPATH_ before any of the tests or script will work.
100+
> **Note**: You must add the _refactoring101_ directory to your _PYTHONPATH_ before any of the tests or script will work.
85101
86102
```bash
87103
$ cd /path/to/refactoring101
@@ -96,7 +112,7 @@ $ export PYTHONPATH=`pwd`:$PYTHONPATH
96112
* What is a class? What is a method?
97113
* What is an object in Python? What is an instance?
98114
* What is the __init__ method on a class used for?
99-
* What is _self_ and how does it relate to class instances?
115+
* What is *self* and how does it relate to class instances?
100116

101117
#### Exercises
102118

@@ -113,63 +129,79 @@ In this section, we create classes that model the real world of
113129
elections. These classes are intended to serve as a more intuitive container
114130
for data transformations and complex bits of logic currently scattered across our application.
115131

116-
The goal is to [hide complexity][] behind simple interfaces.
132+
The goal is to [hide complexity][] behind simple [interfaces][].
117133

118134
We perform these refactorings in a step-by-step fashion and attempt to [write tests before the actual code][].
119135

120-
> NOTE: We've assigned git tags code commits so you can examine the state of affairs at different points.
136+
> NOTE: We've assigned git tags to code commits so you can examine the state of affairs at each step of coding.
121137
> You can [check out these tags][] in your local repo, or view them on github by clicking links in the README.
122138
> Tag links look like this: [elex4.1.0][]
123139
124140
[hide complexity]: http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)
141+
[interfaces]: http://en.wikipedia.org/wiki/Interface_(computing)
125142
[write tests before the actual code]: http://en.wikipedia.org/wiki/Test-driven_development
126143
[check out these tags]: http://githowto.com/tagging_versions
127144
[elex4.1.0]: https://github.com/PythonJournos/refactoring101/tree/elex4.1.0
128145

129-
So how do we start modeling our domain? We clearly have races and candidates, and the results associated with each
130-
candidate.
146+
So how do we start modeling our domain? We clearly have races and candidates, which seem like natural...wait for it...
147+
"candidates" for model classes. We also have county-level results associated with each candidate.
131148

132149
Let's start by creating Candidate and Race classes with some simple behavior.
133-
We''ll eventually flesh out these classes to handle most of the grunt work needed
134-
to produce the summary report.
150+
These classes will eventually be our workhorses, handling most of the grunt work needed
151+
to produce the summary report. But let's start with the basics.
135152

136153

137154
### Candidate model
138155

139156
Candidates have a name, party and county election results. The candidate model also seems like a natural
140-
place for some of the computations that now live in _lib/parser.py_ and _lib/analysis.py_:
157+
place for some of the data transforms and computations that now live in _lib/parser.py_ and _lib/analysis.py_:
141158

142159
* total candiate votes from all counties
143160
* candidate vote percentage
144161
* winner status
145162
* margin of victory, if appropriate
146163

147-
Before we dive into migrating computed values, let's start with the basics.
148-
We've decided to store our new election classes in a models.py (Django users, this should be familiar).
149-
Therefore, we'll store tests in a new *test_models.py* module.
164+
Before we dive into migrating data transforms and computed values, let's start with the basics.
165+
We'll store our new election classes in a *lib/models.py* ([Django[] users, this should be familiar).
166+
We'll store tests in a new *test_models.py* module.
167+
168+
[Django]: https://docs.djangoproject.com/en/dev/topics/db/models
169+
170+
We'll run tests on the command line using the [nose][] library:
150171

151-
We can run those tests by doing the following:
152172
```bash
153173
nosetests -v tests/test_models.py
174+
# or run all tests in the tests/ directory
175+
nosetests -v tests/*.py
154176
```
155177

156-
Now let's start writing some test-driven code.
178+
[nose]: https://nose.readthedocs.org/en/latest/index.html
179+
180+
Now let's start writing some test-driven code!
157181

158182
#### Add name bits
159183

160-
* Create *elex4/tests/test_models.py* and add test for Candidate name handling ([elex4.1.0][])
184+
We'll start by creating a Candidate class that automatically parses a full name into first and last names (remember,
185+
candidate names in our source data are in the form *Lastname, Firstname*).
186+
187+
* Create *elex4/tests/test_models.py* and add test for Candidate name parts ([elex4.1.0][])
161188
* Run test; see it fail
162189
* Write a Candidate class with *first_name* and *last_name* attributes ([elex4.1.1][])
190+
191+
> *Note*: You can cheat here. Recall that the name parsing code was
192+
> already written in *lib/parser.py*.
193+
163194
* Run test; see it pass
164195

165196
[elex4.1.0]: https://github.com/PythonJournos/refactoring101/blob/elex4.1.0/elex4/tests/test_models.py "test_models.py"
166197
[elex4.1.1]: https://github.com/PythonJournos/refactoring101/blob/elex4.1.1/elex4/lib/models.py "lib/models.py"
167198

168-
169199
Let's apply a similar process for the party transformation.
170200

171201
#### Add party
172202

203+
The candidate party requires special handling for Democratics and Republicans. Otherwise we'll default to the raw party value.
204+
173205
* Migrate party-related tests from *tests/test_parser.py* to *TestCandidate* in *tests/test_models.py*.
174206

175207
> **Note**: Don't forget to add the party argument to the Candidate instance in *test_candidate_name*.
@@ -193,56 +225,56 @@ Let's apply a similar process for the party transformation.
193225

194226
##### Observations
195227

196-
In the party refactoring above, notice that we're not directly testing the *clean_party* method but simply checking for the correct value of the *party* attribute on candidate instances. The *clean_party* code has been nicely
228+
In the party refactoring above, notice that we're not directly testing the *clean_party* method but simply
229+
checking for the correct value of the *party* attribute on candidate instances. The *clean_party* code has been nicely
197230
tucked out of sight. In fact, we emphasize that this method is an *implementation detail* --
198-
part of the candidate class's internal housekeeping -- by prefixing it with two underscores.
231+
part of the *Candidate* class's internal housekeeping -- by prefixing it with two underscores.
199232

200233
This syntax denotes a [private method][] that is not intended for use by code outside the
201234
*Candidate* class. We're restricting (though not completely preventing) the outside world from using it,
202-
since it's quite possible this code wil change or be removed entirely in the future.
235+
since it's quite possible this code wil change or be removed in the future.
203236

204-
> More frequently, you'll see a single underscore prefix used to denote private methods.
237+
> More frequently, you'll see a single underscore prefix used to denote private methods and variables.
205238
> This is fine, though note that only the double underscores trigger the name-mangling
206239
> intended to limit usage of the method.
207240
208241
[private method]: http://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
209242

210-
We now have two sets of code (and related tests) for the same functionality. But we're not yet ready
243+
We now have two sets of code (and related tests) for the same functionality. But we're not quite ready
211244
to delete the original *clean_party* function in _lib/parser.py_. Ideally, we'll delete that code and its tests
212-
*after* we've written tests that exercise the summarization logic. That way, we'll have greater confidence that converting from a function-based to a class-based strategy hasn't corrupted the summary numbers.
245+
*after* we've written tests that exercise the summarization logic. That way, we'll have greater confidence that
246+
converting from a function-based to a class-based strategy hasn't corrupted the summary numbers.
213247

214248
##### Questions
215249

216250
* In order to migrate functions to methods on the Candidate class, we had to
217251
make the first parameter in each method *self*. Why?
218252

219-
##### Exercises
220-
221253
### Add vote
222254

223-
Each candidate has a single name and party (well, usually), and numerous county-level results.
255+
Each candidate has a single name and party, and numerous county-level results.
224256
As part of our summary report, county-level results need to be rolled up into a racewide total for each candidate.
225257
At a high level, it seems natural for each candidate to track his or her own vote totals.
226258

227259
Below are a few other basic assumptions, or requirements, that will help us flesh out
228-
vote-handling code on the Candidate class:
260+
vote-handling on the Candidate class:
229261

230262
* A candidate should start with zero votes
231263
* Adding a vote should increment the vote count
232264
* County-level results should be accessible
233265

234266
With this basic list of requirements in hand, we're ready to start coding. For each requirement, we'll start by
235-
writing a (failing) test that captures this assumnption; then we'll write code to make the test pass. The goal
236-
is to capture our assumptions in the form of tests, and then write code required to meet those assumnptions.
267+
writing a (failing) test that captures this assumption; then we'll write code to make the test pass. The goal
268+
is to capture our assumptions in the form of tests, and then write code to meet those assumnptions.
237269

238270
1. Add test for zero vote count as initial Candidate state ([elex4.3.0][])
239271

240272
> Note: We created a new *TestCandidateVotes* class with a *setUp* method that lets us
241273
> re-use the same candidate instance across all test methods. This
242274
> makes our tests less brittle -- e.g., if we add a parameter to the
243-
> Candidate class, we only have to update the candidate instance in
244-
> one the *setUp* method, rather than in every test method (as
245-
> we will have to in the *TestCandidate* class)
275+
> *Candidate* class, we only have to update the candidate instance in
276+
> the *setUp* method, rather than in every test method (as
277+
> we will have to do in the *TestCandidate* class)
246278
247279
1. Run test; see it fail
248280
1. Update Candidate to have initial vote count of zero ([elex4.3.1][])
@@ -282,6 +314,8 @@ is to capture our assumptions in the form of tests, and then write code required
282314

283315
[unittest docs]: http://docs.python.org/2/library/unittest.html
284316

317+
## Race model
318+
285319
## TODO
286320

287321
* Race.office and district
@@ -290,4 +324,8 @@ is to capture our assumptions in the form of tests, and then write code required
290324
* Write high-level tests for summarize output
291325
* Update Parser to return Candidate and Race classes
292326
* Update summary script to use Cand/Race objects returned by Parser class
293-
327+
* Add another tree view of directory; doesn't look so different from
328+
_elex3_ tree view (mainly added models.py and test_models.py), but the underlying implementation has
329+
changed dramatically (hopefully for the better)
330+
* Add disclaimers about our pragmatic approach, vs more disciplined approaches
331+
involving test isolation, etc.

0 commit comments

Comments
 (0)