Quick Intro To BDD Style Table Syntax 286
Here it is a over a year after I wrote several FitNesse tutorials. Several months back Bob added some new ways to write scenarios using a placeholder syntax and an alternative way to render script tables. What you end up with is something just a little less table-like.
This is a very quick and dirty writeup I’ve been meaning to put together for nearly half a year. So while it’s not pretty, it works, shows the basics and may make you aware of something you didn’t know was there – unless you’ve been reading the release notes.
Alternative Script Table Syntax
Comments and requests welcome.
Brett
Some Rough Draft TDD Demonstration Videos 203
I’m doing a series of videos on TDD. The ultimate result will be a much more polished version with embedded slides, and such. But as a part of the development process, I’m creating scratch videos.
Much of what you see in these videos will be in the final versions, but those are far in the future relative to this work.
Hope you find them interesting.
Comments welcome.
Here is what is already available:- Getting started
- Adding Operators
- Removing violation of Open/Closed principle
- Removing duplication in operations with a combination of the Strategy pattern and the Template Method pattern
- Adding new operators after the removal of duplication.
- Reducing coupling by using the Abstract Factory pattern, Dependency Inversion and Dependency Injection
- Adding a few more operations
- Allowing the creation of complex “programs” or “macros” by using the Composite pattern – and avoiding Liskov Substitution Principle inherent in the GoF version of the pattern
- Driving the calculator via FitNesse + Slim
Anyway, that’s the plan. I’ll try to add each of these videos over the next few weeks.
Scala Bowling Kata - still in the middle I suppose 90
"One more roll on a game with 20 rolls and an open 10th frame" should {
20 times { roll(1) }
roll(1) must throwA[IllegalArgumentException]
}
"Two more rolls a game with 10 spares" should {
10 times { spare }
roll(1)
roll(1) must throwA[IllegalArgumentException]
}
"Two marks in the 10th frame should" should {
18 times { roll(1) }
strike
spare
roll(1) must throwA[IllegalArgumentException]
}
On my flight from DFW to SNA, I got these behaviors implemented. The code was pretty ugly!
However, ugly code in hand, passing examples, a slight understanding of some of the problems with my code and a desire to make the BowlingScorer immutable was all I needed to make progress.
I removed the index instance field by rewriting the score method and injecting a tuple into foldLeft (written here using the short-hand notation /:): def scoreAt(frame:Int) =
((0,0) /: (1 to frame)) { (t, _) =>
(t._1 + scoreAtIndex(t._2), t._2 + incrementAt(t._2))
}._1
def onFirstThrow = {
var index = 0
while(index < rolls.length)
if(isStrike(index)) index += 1 else index += 2
index == rolls.length
}
While I am happy I was able to remove the index variable, which was really a parameter being passed around in a field (ugly), I am not happy with this method.
I changed the roll method to return a new instance of a BowlingScorer, making the bowling scorer immutable: def roll(roll:Int) = {
validate(roll)
new BowlingScorer(rolls ++ Array(roll))
}
So I think I’m still somewhere in the middle of working through this code. Again, I’m still learning Scala. I have a lot to learn. I really only barely understand functional programming and, frankly, the Eclipse IDE, while functional, is getting in the way quite a bit. So for a toy example it is OK. Given the choice of this environment or vi and the command line, I’d not pick the former. (I might give the other IDE’s a go, but that’s not really what I’m interested in learning right now.)
So here’s the next version. I plan to work through all of the comments I’ve yet to process from the previous blog posting over the next few days. If you can recommend a better implementation of onFirstThrow, I’d appreciate it.
Other general comments also welcome.
BowlingScorerExampleGroup.scala
package com.om.example
import org.specs._
object BowlingScorerExampleGroup extends SpecificationWithJUnit {
var scorer = new BowlingScorer(Nil);
def roll(value:Int) =
scorer = scorer.roll(value)
def haveAScoreOf(expected:Int) =
scorer.score must_== expected
def strike =
roll(10)
def spare {
roll(5)
roll(5)
}
implicit def intToDo(count: Int) = {
new {
def times(f: => Unit) = {
1 to count foreach { _ => f }
}
}
}
"A Newly Created Bowling Scorer" should {
haveAScoreOf(0)
}
"A game with all 0's" should {
20 times { roll(0) }
haveAScoreOf(0)
}
"A game with all 1's" should {
20 times { roll(1) }
haveAScoreOf(20)
}
"A game with a single spare followed by a 5" should {
spare
roll(5)
haveAScoreOf(20)
}
"A game with all 5's" should {
10 times { spare }
roll(5)
haveAScoreOf(150)
}
"A game with a single strike followed by a 4" should {
strike
roll(4)
haveAScoreOf(18)
}
"A game with a strike, spare then an open frame with two 3's" should {
strike
spare
2 times { roll(3) }
haveAScoreOf(39)
}
"A game with strike, spare then an open frame with two 3's" should {
spare
strike
2 times { roll(3) }
haveAScoreOf(42)
}
"A Dutch 200 game, Spare-Strike" should {
5 times {
spare
strike
}
spare
haveAScoreOf(200)
}
"A Dutch 200 game, Strike-Spare" should {
5 times {
strike
spare
}
strike
haveAScoreOf(200)
}
"A Perfect game" should {
12 times { strike }
haveAScoreOf(300)
}
"The score for each frame of a Perfect game, each frame" should {
12 times { strike }
1 to 10 foreach { frame => scorer.scoreAt(frame) must_== 30 * frame }
}
"An individaul roll of > 10" should {
roll(11) must throwA[IllegalArgumentException]
}
"An iniviaul roll of < 0" should {
roll(-1) must throwA[IllegalArgumentException]
}
"A frame trying to contain more than 10 pins" should {
roll(8)
roll(3) must throwA[IllegalArgumentException]
}
"One more roll on a game with 20 rolls and an open 10th frame" should {
20 times { roll(1) }
roll(1) must throwA[IllegalArgumentException]
}
"Two more rolls a game with 10 spares" should {
10 times { spare }
roll(1)
roll(1) must throwA[IllegalArgumentException]
}
"Two marks in the 10th frame should" should {
18 times { roll(1) }
strike
spare
roll(1) must throwA[IllegalArgumentException]
}
}
BowlingScorer.scala
package com.om.example
class BowlingScorer(rollsSoFar:List[Int]){
val rolls:List[Int] = rollsSoFar
def roll(roll:Int) = {
validate(roll)
new BowlingScorer(rolls ++ Array(roll))
}
def validate(roll:Int) {
if(invalidRoll(roll))
throw new IllegalArgumentException("Individaul rolls must be from 0 .. 10")
if(frameRollTooHigh(roll))
throw new IllegalArgumentException("Total of rolls for frame must not exceed 10");
if(willBeTooManyRolls)
throw new IllegalArgumentException("Game over, no more rolls allowed")
}
def invalidRoll(roll:Int) =
(0 to 10 contains(roll)) == false
def frameRollTooHigh(roll:Int) =
openScoreAt(indexToValidate) + roll > 10
def willBeTooManyRolls =
tenthRolled(indexOf10thFrame) && nextRollTooMany(indexOf10thFrame)
def tenthRolled(tenthIndex:Int) =
tenthIndex < rolls.length
def nextRollTooMany(tenthIndex: Int) =
allowedTenthFrameRolls(tenthIndex) < rollsInTenthFrame(tenthIndex) + 1
def indexOf10thFrame =
(0 /: (1 until 10)) {(c, _) => c + incrementAt(c)}
def allowedTenthFrameRolls(index:Int) =
if(isMark(index)) 3 else 2
def rollsInTenthFrame(index: Int) =
rolls.length - index
def indexToValidate =
if(onFirstThrow) rolls.length else rolls.length - 1
def onFirstThrow = {
var index = 0
while(index < rolls.length)
if(isStrike(index)) index += 1 else index += 2
index == rolls.length
}
def scoreAt(frame:Int) =
((0,0) /: (1 to frame)) { (t, _) =>
(t._1 + scoreAtIndex(t._2), t._2 + incrementAt(t._2))
}._1
def score = scoreAt(10)
def scoreAtIndex(index:Int) =
if(isMark(index)) markScoreAt(index) else openScoreAt(index)
def incrementAt(index:Int) =
if(isStrike(index)) 1 else 2
def isMark(index:Int) =
isStrike(index) || isSpare(index)
def isStrike(index:Int) =
valueAt(index) == 10
def markScoreAt(index:Int) =
sumNext(index, 3)
def isSpare(index:Int) =
openScoreAt(index) == 10 && valueAt(index) != 10
def openScoreAt(index:Int) =
sumNext(index, 2)
def sumNext(index:Int, count:Int) =
(0 /: (index until index+count))(_ + valueAt(_))
def valueAt(index:Int) =
if(rolls.length > index) rolls(index) else 0
}
Scala Bowling Kata - somewhere in the middle... 47
- Installed the Eclipse Scala Plugin
- Installed Scala using Mac Ports
- Figured out how to get those things playing nice (the plugin page pretty much did that, but in a nutshell, add a few jar files to the classpath)
- We don’t need YADT – Yet another damn term
- Trait is a heavily overloaded word
- I like the term BDD better and it fits.
Anyway, one such choice was Specs, which is what I decided to use.
So back to yak shaving:- I added another jar to my classpath in Eclipse
- Then read how to get it running in Eclipse. Not too bad, I suppose.
So now I need to learn Scala. Sure, I’ve used it, but far less than Ruby. So it took me several hours to get specs running along with writing some Scala code to score a game – I’m glad I know the domain at least.
I wanted to make similar behaviors to the ones I wrote for the Ruby version, which I did.
However, unlike the Ruby version, I was curious what would happen if I:- Took an approach similar to Uncle Bob – strikes take one slot in an array
- Added input validation
On the one hand, there are some interesting things I managed to create. On the other hand, I’ve got a bit of a mess. I have a stateful object to avoid passing parameters so that I can write some of the code cleanly. I know I need to add in an intermediate computational object, and I’m going to get to that. However, I wanted to get feedback on what I’ve put out there so far.
Specifically,- What do you think of the (bdd-style) examples from specc?
- What is the correct way to write the Times(20).Do( ...) thing I came up with, there has be a better way?
- For the part of the bowling scoring code that is not stateful (read this as, does not violate the SRP), what do you think of it?
- How would you remove most/all of the state (other than the individual rolls) out of the Bowling scorer class? (Or would you choose to have the roll() method return a new instance of BowlingScorer with the new score recorded?)
- Notice that the class maintains a mini state machine in the form of tracking whether the first ball of he current frame (not tracked) has or has not been thrown. That’s only there to be able to perform input validation. I considered:
- Walking the array
- Going to 2 slots for every frame (making it easy to find the frame)
- Storing a frame object (ok, I didn’t really consider it, but I did think about it)
- The mini state machine
- nextFrameScore uses the index instance variable, and changes it. This both violates command-query separation and demonstrates a violation of the SRP, but it made the scoreAt method look nice.
An interesting side effect is that scoring marks (strikes and spares) uses the same approach, sum up three rolls total.
I know this needs work. What I’ve got works according to its current specification (its examples), so in a sense, that’s a good thing because I’ve already stared experimenting with trying out different solutions. However, I am painfully aware of how unaware I am of Scala at the moment, so your (hopefully gentle) feedback will tell me what I need to learn next.
Looking forward to the virtual beating …
Brett
Here are the two files I’ve created so far (and to be clear, all of the examples pass): BowlingScorerExampleGroup.scalapackage com.om.example
import org.specs._
object BowlingScorerExampleGroup extends SpecificationWithJUnit {
var scorer = new BowlingScorer();
def roll(value:Int) {
scorer.roll(value)
}
def rollMany(rolls:Int, value:Int) {
0.until(rolls).foreach { arg => scorer.roll(value) }
}
def haveAScoreOf(expected:Int) {
scorer.score must_== expected
}
def strike {
roll(10)
}
def spare {
rollMany(2, 5)
}
abstract class IDo {
def Do(block: => Unit)
}
def Times(count:Int): IDo = {
return new IDo {
def Do(block: => Unit) {
1.to(count).foreach( arg => block )
}
}
}
"A Newly Created Bowling Scorer" should {
haveAScoreOf(0)
}
"A game with all 0's" should {
Times(20).Do( roll(0) )
haveAScoreOf(0)
}
"A game with all 1's" should {
Times(20).Do { roll(1) }
haveAScoreOf(20)
}
"A game with a single spare followed by a 5" should {
spare
roll(5)
haveAScoreOf(20)
}
"A game with all 5's" should {
Times(10).Do( spare )
roll(5)
haveAScoreOf(150)
}
"A game with a single strike followed by a 4" should {
strike
roll(4)
haveAScoreOf(18)
}
"A game with a strike, spare then an open frame with two 3's" should {
strike
spare
Times(2).Do( roll(3) )
haveAScoreOf(39)
}
"A game with strike, spare then an open frame with two 3's" should {
spare
strike
Times(2).Do( roll(3) )
haveAScoreOf(42)
}
"A Dutch 200 game, Spare-Strike" should {
Times(5).Do {
spare
strike
}
spare
haveAScoreOf(200)
}
"A Dutch 200 game, Strike-Spare" should {
Times(5).Do {
strike
spare
}
strike
haveAScoreOf(200)
}
"A Perfect game" should {
Times(12).Do( strike )
haveAScoreOf(300)
}
"The score for each frame of a Perfect game, each frame" should {
Times(12).Do( strike )
1.to(10).foreach{ frame => scorer.scoreAt(frame) must_== 30 * frame }
}
"An individaul roll of > 10" should {
roll(11) must throwA[IllegalArgumentException]
}
"An iniviaul roll of < 0" should {
roll(-1) must throwA[IllegalArgumentException]
}
"A frame trying to contain more than 10 pins" should {
roll(8)
roll(3) must throwA[IllegalArgumentException]
}
}
BowlingScorer.scala
package com.om.example
class BowlingScorer {
var rolls:Array[Int] = Array()
var index:Int = 0
var firstBallInFrameThrown: Boolean = false;
def roll(roll:Int) = {
validate(roll)
record(roll)
}
def validate(roll:Int) {
if((0).to(10).contains(roll) == false)
throw new IllegalArgumentException("Individaul rolls must be from 0 .. 10")
if(openScoreAt(indexToValidate) + roll > 10)
throw new IllegalArgumentException("Total of rolls for frame must not exceed 10");
}
def record(roll: Int) {
rolls = rolls ++ Array(roll)
firstBallInFrameThrown = firstBallInFrameThrown == false && roll != 10
}
def indexToValidate = {
if(firstBallInFrameThrown) rolls.length - 1 else rolls.length
}
def scoreAt(frame:Int) = {
1.to(frame).foldLeft(0) { (total, frame) => total + nextFrameScore }
}
def score = {
scoreAt(10)
}
def nextFrameScore = {
var result = 0;
if(isStrike(index)) {
result += markScoreAt(index)
index += 1
} else if(isSpare(index)) {
result += markScoreAt(index);
index += 2
} else {
result += openScoreAt(index);
index += 2
}
result
}
def isStrike(index:Int) = {
valueAt(index) == 10
}
def markScoreAt(index:Int) = {
sumNext(index, 3)
}
def isSpare(index:Int) = {
openScoreAt(index) == 10
}
def openScoreAt(index:Int) = {
sumNext(index, 2)
}
def sumNext(index:Int, count:Int) = {
index.until(index+count).foldLeft(0)(_ + valueAt(_))
}
def valueAt(index:Int) = {
if(rolls.length > index) rolls.apply(index) else 0
}
}
Notes from the OkC Dojo 2009-09-30 69
Tonight we had a small group of die-hard practitioners working with Ruby and RSpec. We intended to use the Randori style, but it was a small enough group that we were a bit more informal than that.
We tried the Shunting Yard Algorithm again and it worked out fairly well. The level of experience in Ruby was low to moderate (which is why we wanted to get people a chance to practice it) and the RSpec experience was generally low (again, great reason to give it a try).
- Forth
- Operator precedence
- Operator associativity
- L-Values and R-Values
- Directed Acyclic Graphis
- In-fix, pre-fix, post-fix binary tree traversal
- Abstract Syntax Trees (AST)
- The list goes on, I’m a big-time extrovert, so I told Chad to occasionally tell me to shut the heck up
- 1 + 3 becomes 1 3 +
- a = b = 17 becomes a b 17 = =
- 2 + 3 * 5 becomes 2 3 5 * +
- 2 * 3 + 5 becomes 2 3 * 5 +
One typical approach to this problem is to develop an AST from the in-fix representation and then recursively traversing the AST using a recursive post-fix traversal.
What I like about he Shunting Yard Algorithm is it takes a traditionally recursive algorithm (DAG traversal, where a binary tree is a degenerate DAG) and writes it iteratively using it’s own stack (local or instance variable) storage versus using the program stack to store activation records (OK stack frames). Essentially, the local stack is used for pending work.
This is one of those things I think is a useful skill to learn: writing traditionally recursive algorithms using a stack-based approach. This allows you to step through something (think iteration) versus having to do things whole-hog (recursively, with a block (lambda) passed in). In fact, I bought a used algorithm book 20 years ago because it had a second on this subject. And looking over my left shoulder, I just saw that book. Nice.
To illustrate, here’s the AST for the first example:
Since the group had not done a lot with recursive algorithms (at least not recently), we discussed a short hand way to remember the various traversal algorithms using three letters: L, R, P
- L -> Go Left
- R -> Go Right
- P -> Print (or process)
- in-fix, in -> in between -> L P R
- pre-fix, pre, before -> P L R
- post-fix, post, after -> L R P
- in-fix: Go left, you hit the 1, it’s a leaf note so print it, go up to the +, print it, go to the right, you end up with 1 + 3
- post-fix: Go left you hit the 1, it’s a leaf node, print it, go back to the +, since this is post-fix, don’t print yet, go to the right, you get the 3, it’s a leaf node, print it, then finally print the +, giving: 1 3 +
- pre-fix: start at + and print it, then go left, it’s a leaf note, print it, go right, it’s a leaf node, print it, so you get: + 1 3 – which looks like a function call (think operator+(1, 3))
It’s not quite this simple – we actually looked at larger examples – but this gets the essence across. And to move from a tree to a DAG, simply iterate over all children, printing before or after the complete iteration; in-fix doesn’t make as much sense in a general DAG. We also discussed tracking the visited nodes if you’ve got a graph versus an acyclic graph.
After we got a multi-operator expression with same-precedence operators working, e.g., 1 + 3 – 2, which results in: 1 3 + 2 -, we moved on to handling different operator precedence.
Around this time, there was some skepticism that post-fix could represent the same expression as in-fix. This is normal, if you have not seen these kinds of representations. And let’s be frank, how often do most of us deal with these kinds of things? Not often.
Also, there was another question: WHY?
In a nutshell, with a post-fix notation, you do not need parentheses. As soon as an operator is encountered, you can immediately process it rather than waiting until the next token to complete the operator (no look-ahead required). This also led to HP developing a calculator in 1967 (or ‘68) that was < 50 pounds and around USD $5,000 that could add, subtract, multiply and divide, which was huge at the time (with a stack size of 3 – later models went to a stack size of 4, giving us the x, y, z and t registers).
During this rat-hole, we discussed associativity. For example, a = b = c is really (a = (b = c))
That’s because the assignment operator is right-associative. This lead into r-values and l-values.
Anyway, we’re going to meet again next week. Because we (read this as me) were not disciplined in following the Randori style, these side discussions lead to taking a long to fix a problem. We should have “hit the reset button” sooner, so next time around we’re going to add a bit more structure to see what happens:- The driver finishes by writing a new failing test.
- The driver commits the code with the newest failing test (we’ll be using git)
- Change drivers and give him/her some time-box (5 – 10 minutes)
- If, at the end of the current time-box, the current driver has all tests passing, go back to the first bullet in this list.
- If at the end, the same test that was failing is still failing, (fist time only) give them a bit more time.
- However, if any other tests are failing, then we revert back to the last check in and switch drivers.
Here’s an approximation of these rules using yuml.me:
(start)->(Create New Failing Test)->(Commit Work)->(Change Drivers) (Change Drivers)->(Driver Working) (Driver Working)-><d1>[tick]->(Driver Working) <d1>[alarm]->(Check Results)->([All Tests Passing])->(Create New Failing Test) (Check Results)->([Driver Broke Stuff])->(git -reset hard)->(Change Drivers) (Check Results)->([First Time Only and Still Only Newest Test Failing])->(Give Driver A Touch More Time)->(Check Results)
Note that this is not a strict activity diagram, the feature is still in beta, and creating this diagram as I did made the results a bit more readable. Even so, I like this tool so I wanted to throw another example in there (and try out this diagram type I have not used before – at least not with this tool, I’ve created too many activity diagrams). If you’d like to see an accurate activity diagram, post a comment and I’ll draw one in Visio and post it.
Anyway, we’re going to try to move to a weekly informal practice session with either bi-weekly or monthly “formal” meetings. We’ll keep switching out the language and the tools. I’m even tempted to do design sessions – NO CODING?! What?! Why not. Some people still work that way, so it’s good to be able to work in different modes.
If you’re in Oklahoma City, hope to see you. If not, and I’m in your town, I’d be interested in dropping into your dojos!
Markup's Where It's At 16
My first word processor ran in under 8K on a Commodore 64 and that included playing pomp and circumstance. It was adequate. It was markup based. When you wanted to view your work, you previewed the results. (When my friend John got a spell checker add-on, I was really impressed!)
Next, I started using Apple Write (I think that was the name). All of those dot commands on the left margin. I got to the point where I didn’t need to preview, I knew what my stuff was going to look like.
After that, I worked with Word Star on a CP/M emulator running on Apple IIE’s. I actually liked Word Star. I still remember the ^k madness.
But then there was WordPerfect. To this day it is my favorite Word Processor (that’s WordPerfect 4.2 for DOS). I taught classes on it while I was working on a CS degree. I had the 40 function keys memorized and even the F11 and F12 keys – when I had keyboards with F11 and F12.
Here’s the thing, WordPerfect was logical. There were three general categories of markup (codes) and those three categories, when added, went to specific places on the line (home back-arrow, home-home back-arrow, home-home-home back-arrow) and it was related to the scope of the thing. And you could see them with revel codes, Alt-F3. Sure it took a bit of learning but it was expert friendly.
At the same time that Ultima IV came out, I decided to write a book for one of the classes I taught. The first week some friends of mine and I finished Ultima IV – we did not stop playing, but played in shifts. I finished the outline that week. The next week I wrote the first version of a book. I still have one physical copy of that book.
I did this work (Ultima IV and WordPerfect) on my Leading Edge, 2 – 5.25” floppy drive system. The first version was about 170 pages, the final was around 350 pages. Printing took about 3 hours. 1 hour to merge the master document, update the TOC, generate the index, 2 hours to print on my HP DeskJet (the first one).
WordPerfect never crashed (4.2 was solid, 4.0 no so much), and I knew what the resulting print was going to look like before it printed. I groked it. I had lots of formatting, graphics (mostly had-drawn bitmaps), numbered items, tutorials, chapters, page numbers, you know, stuff.
In the mid 80’s I started using Microsoft Word. I didn’t like it then and to this day I do not like it. Anybody who has ever tried working with numbered lists across versions understands what I’m talking about (this feature worked perfectly in WordPerfect). I can get it to work. And I know that when to grab the hidden character at the end of a paragraph (or not) when copying – which leads to selecting paragraphs in a particular direction. I work to avoid using it as much as I can, which is getting better all the time.
I’m not a big fan of complex formatting simply because I have not found anything so clear as WordPerfect. The later versions didn’t do it for me.
I am a fan of using Wiki’s because part of the whole reason to use one is to keep away from messing around with formatting and get to writing.
Lately I’ve been learning CSS. It’s powerful. It’s also magic. Working with flow layouts reminds me of the grid-bag-layout of X fame (or was that Motif – certainly Java has one as well). I like what I can do with it.
I like that I can get the basic content looking decent and then when I’m in the mood, I can attempt to cast spells to make the stuff I’ve written look better. So far, I’m a rank amateur.
I probably should be spending more time re-learning JavaScript (which is a cool language – I love prototype-based languages – first one I used was Self). But I’ve been working on some tutorials for Ruby, one on practicing TDD and a few on BDD.
These are low-level. For example, I have not used the story tests of RSpec yet. That’s the next tutorial – I still have the second BDD tutorial to finish. I already have stories for the next tutorial, so writing story tests and working my way into a solution should be a blast.
If you’d like to have a look, check out: http://schuchert.wikispaces.com/ruby.tutorials
These are works in progress!
To bring the train track back into a complete oval – we want the train to run after all – I came across something from James Martin (http://martinfowler.com/bliki/DuplexBook.html) and it got me to thinking. In these tutorials, I have a lot of “sidebars” where I give a little more context. Those sidebars are scattered throughout.
In 2001 I took a writers workshop with Jerry Weinberg (http://geraldmweinberg.com/Site/Communication.html) and in that workshop we had one exercise involving taking paragraphs from other people on various subjects and attempting to weave a common theme throughout.
Martin’s idea of a duplex book and Weinberg’s writing exercise led me to refactor all of those tutorials such that the sidebars are included files. This leads to a summary of all of the sidebars so far: http://schuchert.wikispaces.com/ruby.sidebars
I think I’m going to take Martin’s message to heart and have 2-views into this material; the sidebars and the tutorials. I’m going to try that exercise I learned from Weinberg and see where it takes me.
How does this relate? As I was refactoring, I made sure to put a span around the titles so I could have consistent, and externally defined formatting in my CSS. So it’s loosely related. I’ve been working with spans and included files a bunch this week.
So what do you think? Is the name of this blog appropriate?
Goodnight!