"What sir, would you make a ship sail against the wind and currents by lighting a bonfire under her deck? I pray you excuse me. I have no time to listen to such nonsense."-- Napoleon Bonaparte
Rants on the daily grind of building software. This has been moved to https://slott56.github.io. Fix your bookmarks.
Moved
Moved. See https://slott56.github.io. All new content goes to the new site. This is a legacy, and will likely be dropped five years after the last post in Jan 2023.
Thursday, March 10, 2011
To Robert Fulton, Regarding the "Steam Boat"
Thursday, March 3, 2011
Improving the "Velocity" of IT
Tuesday, March 1, 2011
DIY and the Dumb Rules of Prevention
Thursday, February 17, 2011
TDD -- From SME Spreadsheet to TestCase to Code
| Latitude 1 | Longitude 1 | Latitude 2 | Longitude 2 | range | bearing |
|---|---|---|---|---|---|
| 50 21 50N | 004 09 25W | 42 21 04N | 071 02 27W | 2805 nm | 260 07 38 |
from __future__ import division
import csv
from collections import namedtuple
import re
latlon_pat= re.compile("(\d+)\s+(\d+)\s+(\d+)([NSWE])")
def latlon( txt ):
match= latlon_pat.match( txt )
d, m, s, h = match.groups()
return float(d)+float(m)/60+float(s)/3600, h
angle_pat= re.compile("(\d+)\s+(\d+)\s+(\d+)")
def angle( txt ):
match= angle_pat.match( txt )
d, m, s = match.groups()
return float(d)+float(m)/60+float(s)/3600
range_pat= re.compile("(\d+)\s*(\D+)")
def range( txt ):
match= range_pat.match( txt )
d, units = match.groups()
return float(d), units
RangeBearing= namedtuple("RangeBearing","lat1,lon1,lat2,lon2,rng,brg")
def test_iter( filename="sample_data.csv" ):
with open(filename,"r") as source:
rdr= csv.DictReader( source )
for row in rdr:
print row
tc= RangeBearing(
latlon(row['Latitude 1']), latlon(row['Longitude 1']),
latlon(row['Latitude 2']), latlon(row['Longitude 2']),
range(row['range']),
angle(row['bearing'])
)
yield tc
for tc in test_iter():
print tc
from string import Template
testcase= Template("""
class Test_${name}( unittest.TestCase ):
def setUp( self ):
self.p1= LatLon( lat=GlobeAngle(*$lat1), lon=GlobeAngle(*$lon1) )
self.p2= LatLon( lat=GlobeAngle(*$lat2), lon=GlobeAngle(*$lon2) )
def test_should_compute( self ):
d, brg = range_bearing( p1, p2, R=$units )
self.assertEquals( $dist, int(d) )
self.assertEquals( $brg, map(int,map(round,brg.deg)))
""")
for name, tc in enumerate( test_iter() ):
units= tc.rng[1].upper()
dist= tc.rng[0]
code= testcase.substitute( name=name, dist=dist, units=units, **tc._asdict() )
print code
Tuesday, February 8, 2011
Unit Test Case, Subject Matter Experts and Requirements
t = SomeTemplate()
for case_dict in testCaseParser( "some.xls" ):
code= t.render( **case_dict )
with open(testcaseName(**case_dict ),'w') as result:
result.write( code )
Wednesday, February 2, 2011
Escaping the Relational Schema Trap
Important Lesson Learned. When the customer misunderstands the offering and asks for something completely senseless, it's good to listen and try to build that -- even if it wasn't what you were offering. In some cases, the original offering was too complex or contrived. In other cases, the offering didn't create enough value. But when you offer [X] and the customer asks how much it will cost for [Y], you have disruptive, sudden, and surprising database changes.
Thursday, January 27, 2011
FAERIE DUST™
Here's how to recognize a Faerie Dust request:
- We have identified a problem. It can be with almost anything: scalability, reliability, auditability, any Quality Measure.
- We're pursuing a specific technology. Typically, something that has the lowest impact on our architecture.
- We can't address anything other than this specific technology variation -- we can't change the application software or buy hardware.
Once we're in the Faerie Dust realm, what can we do?
Laughing doesn't help. They have a serious problem, they need a solution. The fact that they won't address the cause isn't completely relevant -- we have to work on the denial, anger, negotiation, depression cycle first. Hopefully skipping past the anger, or assuring the anger is directed elsewhere.
Helping doesn't help. If we join the quest for their Faerie Dust, what will we accomplish? We'll burn billable hours to -- eventually -- reach an equivocal non-solution with a complex write-up and recommendations that won't be implemented.
Not helping doesn't help. If we obstinately refuse to join the quest for the Faerie Dust... well... then we've done nothing. We haven't advanced their understanding of their problem.
What's left? Is there a middle road that allows us to join the Faerie Dust quest, but still point out the side roads, other monsters and other treasures along the way?
Perhaps there is, but it would require a kind of saintly patient persistence. We would have to start with an enumeration of problem causes, prioritize them, and then focus on their selected bit of Faerie Dust. My idea is that enumerating the possible causes allows us to identify the missed opportunities, and the possible magnitude of fixing something essential (algorithm or data structure) instead of throwing up window-dressing to cover problems in something inessential (reducing the time required for a table scan).
Example
Here's a concrete example of Faerie Dust.
- Pick a data model that doesn't fit the use cases. i.e., lumped many discrete details into a single text field that has "rich semantic content". Work around this mistake by using wild-card matches.
- Complained about performance and dug into nuanced details of LIKE clause and full-text search. Lots of study time spent on LIKE clause processing and how to improvement performance.
- Refused to discuss the actual use case or the mismatch between data structures and requirements.
The design didn't match the use cases. Faerie Dust won't help.