Posted by Sam on Jun 25, 2008 at 08:01 AM UTC - 5 hrs
I don't like to have too many microposts on this blog, so I've decided to save them up and start
a Programming Quotables series. The idea is that I'll post quotes about programming that have one or more of the
following attributes:
- I find funny
- I find asinine
- I find insightfully true
- And stand on their own, with little to no comment needed
Here's the seventh in that series. I hope you enjoy them as much as I did:
More...
In the software industry, we've been chasing quality for years. The interesting thing is there are a number of things that work. Design by Contract works. Test Driven Development works. So do Clean Room, code inspections and the use of higher-level languages.
All of these techniques have been shown to increase quality. And, if we look closely we can see why: all of them force us to reflect on our code.
That's the magic, and it's why unit testing works also. When you write unit tests, TDD-style or after your development, you scrutinize, you think, and often you prevent problems without even encountering a test failure.
Perhaps I've made it seem like I'm on the side of the pirates. Just to make it clear that I'm not sailing under the jolly roger: In my own view, piracy is wrong. It's wrong even when the people making and selling the game are senseless, self-destructive fools. It's wrong even if the game sucks. It's wrong if you're broke. It's wrong even if "you weren't going to buy it anyway." It's wrong and I don't do it, ever.
It is not my intention to preach at pirates and get them to change their habits. I'm not anyone's mum, and it's not my place to tell people how to act. I actually think that having lots of people repent of piracy right now would be horrible. The managers would conclude their monstrous policies were working, and we'd get a double helping of the same, forever after, in every game they put out.
Regardless of the approach taken, I definitely no longer believe that sprocs should play any significant role in any application. The current mandate in the software industry is to strive to lower costs by increasing developer productivity and ORM's clearly help to do this by eliminating the need to write and maintain countless simple CRUD sprocs.
It's definitely time for all of us .NET developers to abandon our convention sproc wisdom and start playing catch-up with the rest of the industry when it comes to using ORM's.
I am not at the mercy of some big up-front UML diagrams or "non-agile" models grounded in getting something wrong in its entirety and very thoroughly before you take measures to fix it (or even begin to detect it).
Hey! Why don't you make your life easier and subscribe to the full post
or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate
wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!
Last modified on Jun 25, 2008 at 08:03 AM UTC - 5 hrs
Posted by Sam on Feb 18, 2008 at 06:43 AM UTC - 5 hrs
Last week, hgs asked,
I find it interesting that lots of people write about how to produce clean code,
how to do good design, taking care about language choice, interfaces, etc, but few people
write about the cases where there isn't time... So, I need to know what are the forces that tell you
to use a jolly good bodge?
I suspect we don't hear much about it because these other problems are often caused by that excuse.
And, in the long run, taking on that technical debt will likely cause you to go so slow that that's the
more interesting problem. In other words, by ignoring the need for good code, you are jumping into
a downward spiral where you are giving yourself even less time (or, making it take so long to do anything
that you may as well have less time).
More...
I think the solution is to start under-promising and over-delivering, as opposed to how most of us do it
now: giving lowball estimates because we think that's what they want to hear. But why lie to them?
If you're using iterative and incremental development, then if you've over-promised one iteration, you
are supposed to dial down your estimates for what you can accomplish in subsequent iterations, until
you finally get good at estimating. And estimates should include what it takes to do it right.
That's the party-line answer to the question. In short: it's never OK to write sloppy code, and
you should take precautions against ever putting yourself in a situation where those
viscous forces pull you in that direction.
The party-line answer is the best answer, but it doesn't fully address the question, and I'm not
always interested in party-line answers anyway. The viscosity (when it's easier
to do the wrong thing that the right thing) is the force behind the bodge. I don't like it, but I
recognize that there are going to be times you run into it and can't resist.
In those cases where you've already painted yourself into a corner, what then? That's the interesting
question here. How do you know the best
places to hack crapcode together and ignore those things that may take a little longer in the short run, but
whose value shows up in the long run?
The easy answer is the obvious one: cut corners in the code that is least likely to need to change or
be touched again. That's because (assuming your hack works) if we don't have to look at the code again,
who really cares that it was a nasty hack? The question whose answer is not so easy or
obvious is "what does such a place in the code look like?"
By the definition above, it would be the lower levels of your code. But if you do that, and inject a bug, then
many other parts of your application would be affected. So maybe that's not the right place to do it.
Instead, it would be better to do it in the higher levels, on which very little (if any) other code
depends. That way, you limit the effects of it. More importantly, if there are no outgoing dependencies
on it, it is easier to change than if other code were highly dependent on it. [ 1]
Maybe the crapcode can be isolated: if a class is already aweful, can you derive a new class from it and
make any new additions with higher quality? If a class is of high quality and you need to hack something together,
can you make a child class and put the hack there? [ 2]
Uncle Bob recently discussed when unit and acceptance
testing may safely be done away with. He put those numbers around 10 and a few thousand lines of code,
respectively.
In the end, there is no easy answer that I can find where I would definitively say, "that's the place for a bodging."
But I suspect there are some patterns we can look for, and I tried to identify a couple of those above.
Do you have any candidates you'd like to share?
Notes:
[1] A passing thought for which I have no answers:
The problem with even identifying those places is that by hacking together solutions, you are more likely
to inject defects into the code, which makes it more likely you'll need to touch it again.
[2] I use inheritance here because the new classes should be
able to be used without violating LSP.
However, you may very well be able to make those changes by favoring composition.
If you can, I'd advocate doing so.
Posted by Sam on Jan 14, 2008 at 06:42 AM UTC - 5 hrs
This is a story about my journey as a programmer, the major highs and lows I've had along the way, and
how this post came to be. It's not about how ecstasy made me a better programmer, so I apologize if that's why you came.
In any case, we'll start at the end, jump to
the beginning, and move along back to today. It's long, but I hope the read is as rewarding as the write.
A while back,
Reg Braithwaite
challenged programing bloggers with three posts he'd love to read (and one that he wouldn't). I loved
the idea so much that I've been thinking about all my experiences as a programmer off and on for the
last several months, trying to find the links between what I learned from certain languages that made
me a better programmer in others, and how they made me better overall. That's how this post came to be.
More...
The experiences discussed herein were valuable in their own right, but the challenge itself is rewarding
as well. How often do we pause to reflect on what we've learned, and more importantly, how it has changed
us? Because of that, I recommend you perform the exercise as well.
I freely admit that some of this isn't necessarily caused by my experiences with the language alone - but
instead shaped by the languages and my experiences surrounding the times.
One last bit of administrata: Some of these memories are over a decade old, and therefore may bleed together
and/or be unfactual. Please forgive the minor errors due to memory loss.
How QBASIC Made Me A Programmer
As I've said before, from the time I was very young, I had an interest in making games.
I was enamored with my Atari 2600, and then later the NES.
I also enjoyed a playground game with Donald Duck
and Spelunker.
Before I was 10, I had a notepad with designs for my as-yet-unreleased blockbuster of a side-scrolling game that would run on
my very own Super Sanola game console (I had the shell designed, not the electronics).
It was that intense interest in how to make a game that led me to inspect some of the source code Microsoft
provided with QBASIC. After learning PRINT, INPUT,
IF..THEN, and GOTO (and of course SomeLabel: to go to)
I was ready to take a shot at my first text-based adventure game.
The game wasn't all that big - consisting of a few rooms, the NEWS
directions, swinging of a sword against a few monsters, and keeping track of treasure and stats for everything -
but it was a complete mess.
The experience with QBASIC taught me that, for any given program of sufficient complexity, you really only
need three to four language constructs:
- Input
- Output
- Conditional statements
- Control structures
Even the control structures may not be necessary there. Why? Suppose you know a set of operations will
be performed an unknown but arbitrary amount of times. Suppose also that it will
be performed less than X number of times, where X is a known quantity smaller than infinity. Then you
can simply write out X number of conditionals to cover all the cases. Not efficient, but not a requirement
either.
Unfortunately, that experience and its lesson stuck with me for a while. (Hence, the title of this weblog.)
Side Note: The number of language constructs I mentioned that are necessary is not from a scientific
source - just from the top of my head at the time I wrote it. If I'm wrong on the amount (be it too high or too low), I always appreciate corrections in the comments.
What ANSI Art taught me about programming
When I started making ANSI art, I was unaware
of TheDraw. Instead, I opened up those .ans files I
enjoyed looking at so much in MS-DOS Editor to
see how it was done. A bunch of escape codes and blocks
came together to produce a thing of visual beauty.
Since all I knew about were the escape codes and the blocks (alt-177, 178, 219-223 mostly), naturally
I used the MS-DOS Editor to create my own art. The limitations of the medium were
strangling, but that was what made it fun.
And I'm sure you can imagine the pain - worse than programming in an assembly language (at least for relatively
small programs).
Nevertheless, the experience taught me some valuable lessons:
- Even though we value people over tools, don't underestimate
the value of a good tool. In fact, when attempting anything new to you, see if there's a tool that can
help you. Back then, I was on local BBSs, and not
the 1337 ones when I first started out. Now, the Internet is ubiquitous. We don't have an excuse anymore.
-
I can now navigate through really bad code (and code that is limited by the language)
a bit easier than I might otherwise have been able to do. I might have to do some experimenting to see what the symbols mean,
but I imagine everyone would.
And to be fair, I'm sure years of personally producing such crapcode also has
something to do with my navigation abilities.
-
Perhaps most importantly, it taught me the value of working in small chunks and
taking baby steps.
When you can't see the result of what you're doing, you've got to constantly check the results
of the latest change, and most software systems are like that. Moreover, when you encounter
something unexpected, an effective approach is to isolate the problem by isolating the
code. In doing so, you can reproduce the problem and problem area, making the fix much
easier.
The Middle Years (included for completeness' sake)
The middle years included exposure to Turbo Pascal,
MASM, C, and C++, and some small experiences in other places as well. Although I learned many lessons,
there are far too many to list here, and most are so small as to not be significant on their own.
Therefore, they are uninteresting for the purposes of this post.
However, there were two lessons I learned from this time (but not during) that are significant:
-
Learn to compile your own $&*@%# programs
(or, learn to fish instead of asking for them).
-
Stop being an arrogant know-it-all prick and admit you know nothing.
As you can tell, I was quite the cowboy coding young buck. I've tried to change that in recent years.
How ColdFusion made me a better programmer when I use Java
Although I've written a ton of bad code in ColdFusion, I've also written a couple of good lines
here and there. I came into ColdFusion with the experiences I've related above this, and my early times
with it definitely illustrate that fact. I cared nothing for small files, knew nothing of abstraction,
and horrendous god-files were created as a result.
If you're a fan of Italian food, looking through my code would make your mouth water.
DRY principle?
Forget about it. I still thought code reuse meant copy and paste.
Still, ColdFusion taught me one important aspect that got me started on the path to
Object Oriented Enlightenment:
Database access shouldn't require several lines of boilerplate code to execute one line of SQL.
Because of my experience with ColdFusion, I wrote my first reusable class in Java that took the boilerplating away, let me instantiate a single object,
and use it for queries.
How Java taught me to write better programs in Ruby, C#, CF and others
It was around the time I started using Java quite a bit that I discovered Uncle Bob's Principles of OOD,
so much of the improvement here is only indirectly related to Java.
Sure, I had heard about object oriented programming, but either I shrugged it off ("who needs that?") or
didn't "get" it (or more likely, a combination of both).
Whatever it was, it took a couple of years of revisiting my own crapcode in ColdFusion and Java as a "professional"
to tip me over the edge. I had to find a better way: Grad school here I come!
The better way was to find a new career. I was going to enter as a Political Scientist
and drop programming altogether. I had seemingly lost all passion for the subject.
Fortunately for me now, the political science department wasn't accepting Spring entrance, so I decide to
at least get started in computer science. Even more luckily, that first semester
Venkat introduced me to the solution to many my problems,
and got me excited about programming again.
I was using Java fairly heavily during all this time, so learning the principles behind OO in depth and
in Java allowed me to extrapolate that for use in other languages.
I focused on principles, not recipes.
On top of it all, Java taught me about unit testing with
JUnit. Now, the first thing I look for when evaluating a language
is a unit testing framework.
What Ruby taught me that the others didn't
My experience with Ruby over the last year or so has been invaluable. In particular, there are four
lessons I've taken (or am in the process of taking):
-
The importance of code as data, or higher-order functions, or first-order functions, or blocks or
closures: After learning how to appropriately use
yield, I really miss it when I'm
using a language where it's lacking.
-
There is value in viewing programming as the construction of lanugages, and DSLs are useful
tools to have in your toolbox.
-
Metaprogramming is OK. Before Ruby, I used metaprogramming very sparingly. Part of that is because
I didn't understand it, and the other part is I didn't take the time to understand it because I
had heard how slow it can make your programs.
Needless to say, after seeing it in action in Ruby, I started using those features more extensively
everywhere else. After seeing Rails, I very rarely write queries in ColdFusion - instead, I've
got a component that takes care of it for me.
-
Because of my interests in Java and Ruby, I've recently started browsing JRuby's source code
and issue tracker.
I'm not yet able to put into words what I'm learning, but that time will come with
some more experience. In any case, I can't imagine that I'll learn nothing from the likes of
Charlie Nutter, Ola Bini,
Thomas Enebo, and others. Can you?
What's next?
Missing from my experience has been a functional language. Sure, I had a tiny bit of Lisp in college, but
not enough to say I got anything out of it. So this year, I'm going to do something useful and not useful
in Erlang. Perhaps next I'll go for Lisp. We'll see where time takes me after that.
That's been my journey. What's yours been like?
Now that I've written that post, I have a request for a post I'd like to see:
What have you learned from a non-programming-related discipline that's made you a better programmer?
Last modified on Jan 16, 2008 at 07:09 AM UTC - 5 hrs
Posted by Sam on Nov 22, 2007 at 12:04 PM UTC - 5 hrs
Since the gift buying season is officially upon us, I thought I'd pitch in to the rampant consumerism and list some of the toys I've had a chance to play with this year that would mean fun and learning for the programmer in your life. Plus, the thought of it sounded fun.
Here they are, in no particular order other than the one in which I thought of them this morning:
More...
- JetBrains' IntelliJ IDEA: An awesome IDE for Java. So great, I don't mind spending the $249 (US) and using it over the free Eclipse. The Ruby plugin is not too shabby either, the license for your copy is good for your OSX and Windows installations, and you can try it free for 30 days. Martin Fowler thinks IntelliJ changed the IDE landscape. If you work in .NET, they also have ReSharper, which I plan to purchase very soon. Now if only we could get a ColdFusion plugin for IntelliJ, I'd love it even more.
- Programming Ruby, Second Edition: What many in the Ruby community consider to be Ruby's Bible. You can lower the barrier of entry for your favorite programmer to using Ruby, certainly one of the funner languages a lot of people are loving to program in lately. Sometimes, I sit and think about things to program just so I can do it in Ruby.
If they've already got that, I always like books as gifts. Some of my
favorites from this year have been: Code Complete 2, Agile Software Development: Principles, Patterns, and Practices which has a great section on object oriented design principles, and of course,
My Job Went to India.
I have a slew of books I've yet to read this year that I got from last Christmas (and birthday), so I'll have to
list those next year.
-
Xbox 360 and a subscription to
XNA Creator's Club (through Xbox Live Marketplace - $99 anually) so they can deploy their games to their new Xbox. This is without a
doubt the thing I'd want most, since I got into this whole programming thing because I was interested
in making games. You can point them to the
getting started page, and they could
make games for the PC for free, using XNA (they'll need that page to get started anyway, even if you
get them the 360 and Creator's Club membership to deploy to the Xbox).
-
MacBook Pro and pay for the extra pixels. I love mine - so much so,
that I intend to marry it. (Ok, not that much, but I have
been enjoying it.)
The extra pixels make the screen almost as wide as two, and if you can get them an extra monitor I'd do
that too. I've moved over to using this as my sole computer for development, and don't bother with
the desktops at work or home anymore, except on rare occasions. You can run Windows on it, and the
virtual machines are getting really good so that you ought not have to even reboot to use either
operating system.
Even if you don't want to get them the MacBook, a second or third monitor should be met with enthusiasm.
-
A Vacation: Programmers are notoriously working long hours
and suffering burnout, so we often need to take a little break from the computer screen. I like
SkyAuction because all the vacations are package deals, there's often a good variety to choose from (many
different countries), most of the time you can find a very good price, and usually the dates are flexible
within a certain time frame, so you don't have to commit right away to a certain date.
Happy Thanksgiving to those celebrating it, and thanks to all you who read and comment and set me straight when I'm wrong - not just here but in the community at large. I do appreciate it.
Do you have any ideas you'd like to share (or ones you'd like to strike from this list)?
Last modified on Nov 22, 2007 at 12:04 PM UTC - 5 hrs
Posted by Sam on Nov 19, 2007 at 04:32 PM UTC - 5 hrs
At the O'Reilly ONLamp blog, chromatic pointed out that we should
"program as if [our] maintenance programmer were not a barely-competent monkey,"
quoting a paragraph from Mark-Jason Dominus.
Mark was describing the idiocy of programming-by-superstition, where you might put parentheses if you
are unsure of the order of operations operator precedence (or, to help barely-competent monkeys know the order precedence), rather
than re-defining that order as opposed to using them to change the "normal" precedence.
More...
If the parentheses are there to clarify a complex expression, that is one thing (but there are
better ways to do it, such as using descriptive variable names).
The parentheses add clutter, making the code ever-so-slightly harder to read.
Certainly no one would implement a scanner for string searching when a simple regular
expression would do (I hope, anyway). It would be even more foolish if you did it just because
the next coder to look at the code might find it easier to understand.
When I first read chromatic's funny phrase, I thought we should program as if those who
follow us have unintelligible thoughts in their heads. (I still do.) I was looking at it like this:
If I write programs like a monkey would write programs (and I've been known to do such things), the
person following me would need a highly developed brain to fit
enough of it in his head to be of any use to the code he inherited. I don't even like to work on some of that old stuff, and I certainly don't understand half of it. Therefore, it would be
better to write the code so that even a monkey can follow it. I'm not talking about concepts
like Mark was talking about. Instead, I'm talking about things like five-line methods, cohesion,
modularity, understandable code, and the sort.
I think both views work - they are just referring to different things.
Maintenance programmers
get dumped on all the time. The least we could do is try to make their jobs easier by not feeding
them crapcode. Maybe a Maintenance Programmer Appreciation Day is in order?
Update: Based on the discussion below, I made a couple of changes to avoid confusion above. Read the comments for more info on that discussion.
Last modified on Nov 23, 2007 at 08:44 AM UTC - 5 hrs
Posted by Sam on Aug 23, 2007 at 09:16 PM UTC - 5 hrs
The next few days in Houston are busy for programming technophiles. A couple of quick reminders:
BarCampHouston 2 is this Saturday, August 25, 2007 beginning at 9:00 AM at Houston Technology Center.
Update: I had the map wrong since it was wrong on the BarCampHouston wiki page. I hope no one went to the wrong place. Here is the correct one: HTC.
I also decided to take the day off and chill out instead of heading up there.
My apologies to anyone who had planned to say hello!
HouCFUG is hosting a ColdFusion 8 release party on Tuesday, August 28 from noon to 1:00 PM at Ziggy's Healthy Grill where they'll be giving away a licensed copy of CF 8.
Finally, Agile Houston is hosting a session with Robert Martin, object mentor on Tuesday as well. It's at 6:30 PM in PGH 563 on the University of Houston Campus.
I should be at both BarCamp and at Robert's presentation, but I'll be in class during HouCFUG's meeting.
Last modified on Aug 25, 2007 at 09:28 AM UTC - 5 hrs
Posted by Sam on Jul 27, 2007 at 03:43 PM UTC - 5 hrs
We could all stand to be better at what we do - especially those of us who write software. Although
many of these ideas were not news to me, and may not be for you either, you'd be surprised at how
you start to slack off and what a memory refresh will do for you.
Here are (briefly)
10 ways to improve your code from the
NFJS
session I attended with Neal Ford.
Which do you follow?
More...
-
Know the fundamentals
It was no surprise that Neal led off with the DRY
principle. Being one from the school of thought that "code reuse" meant "copy-and-pastable with few changes," I
feel this is the most important principle to follow when coding.
Neal uses it in the original sense:
Refuse to repeat yourself not just in your code, but all throughout the system.
To quote (If I remember correctly, The Pragmatic Programmer): "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system."
I can't think of anything that wouldn't be improved by following that advice.
Continuous integration and version control are important fundamentals as well. Continuous integration means you
know the code base at least compiles, and you can have it run your unit tests as well. The codebase is verified as always working
(at least to the specs as you wrote them - you might have gotten them wrong, of course).
Using version control means you don't have to be afraid of deleting that commented out code that you'll probably never use again but
you keep around anyway "just in case." That keeps your code clean.
Finally, Neal suggests we should use static analysis tools such as FindBugs
and PMD that inspect our code, look for bad patterns, and suggest fixes and
places to trim. Those two are for Java. Do you know of similar applications for other platforms? (Leave a comment please!)
-
Analyze code odors and remove them if they stink
Inheritance is a smell. It doesn't always have to smell bad, but at least you should take the time
to determine if what you really need is composition.
Likewise, the existence of helper or utility classes often indicates a "poor design." As
Neal notes, "if you've gotten the abstraction correct, why do you need 'helpers'?" Try to put
them in "intelligent domain object[s]" instead.
Static methods are an indication you are thinking procedurally, and they are "virtually impossible to test."
Seeing the procedural mindset is easy, I think, but why are they hard to test?
Finally, avoid singletons. I know Singleton is everyone's favorite design
pattern because it's so easy to conceptualize, but as Neal mentions:
- They're really just a poor excuse for global variables.
- They violate SRP (link is to a PDF file)
by mixing business logic with policing you from using too many
- They make themselves hard to test
- It's very hard to build a real singleton, as you have to ensure you are using a unified class loader.
- More that I won't get into here...
-
Kill Sacred Cows
Neal started off saying that "sacred cows make the tastiest hamburger" and told the story of the angry monkeys:
A few monkeys were put in a room with a stepladder and a banana that could only be reached if the
monkeys used the stepladder. However, when a monkey would climb upon the stepladder, all the
monkeys would be doused with water. Then new monkeys were introduced and when they
tried to use the stepladder, the other monkeys would get angry and beat him up. Eventually,
the room contained none who had been doused with water, but the monkeys who had been beat up for
using the stepladder were still refusing to allow new monkeys to get the banana.
That is the light in which you may often view sacred cows in software development. In particular,
Neal points out that
- StickingToCamelCaseForSentenceLongTestNamesIsRidiculous so_it_is_ok_to_use_underscores
in languages_that_standardized_on_camel_case for your test names.
-
Using getters and setters does not equate to encapsulation. One of my favorite sayings,
and a reference to why
they are evil is appropriate.
-
Avoidance of multiple returns in a method is antiquated because it comes from a time
when we weren't using tiny, cohesive methods. I still like to avoid them, but will
use them when it makes the code more readable.
-
Polluting interface names with "I", as in ISomeInterface should be avoided.
It is contrary to what interfaces are about. Instead, you should decorate the concrete
class name.
-
Your objects should be Good Citizens. Never let them exist in an invalid state.
-
Use Test Driven Development
TDD provides "explicit dependency management" because you must think about dependencies
as you code. It provides instant feedback and encourages you to implement the simplest
thing that works.
You can take it further by analyzing your dependencies with JDepend.
And don't forget about YAGNI. Quoting
Neal,
Speculative development saves time if
• You have nothing better to work on right now
• You guarantee that it won't take longer to fix later
Is that ever going to be true?
-
Use reflection when you can (and when it makes sense)
It's not slow, and it can be extremely useful!
-
Colour Your World
Nick Drew, a ThoughtWorker in the UK came up with a system of coloring the code you write
by classifying it as to who will use the feature: only a specific business customer,
only a particular market, many markets, or unchangeable infrastructure. Based on
the color and amount of it, you can decide what value or cost your code has.
It's a very interesting system, so I recommend seeing a more detailed overview in
Neal's handout (PDF).
You can find it on pages 21-22.
-
Use a DSL style of coding
It lets you "[Build] better abstraction layers, using language instead of trees" while
"utilizing" current building blocks. As he pointed out, "every complicated human endeavor
has its own DSL. [If you can] make the code closer to the DSL of the business [then] it is
better abstracted."
I'll add that it's easier to spot logical errors as well.
A funny quote from Neal: "It's almost as if in Java we're talking to a severely retarded person."
Regarding that, he recommended looking at EasyMock as a good
example of fluent interfaces, and that having setters return void in a waste.
Instead, we should return this so that calls can be chained together (and if
you are using fluent-interface type names, you could construct sentences in your DSL that way).
Neal also noted a distinction between an API and DSL: API has an explicit context that must
be repeated with each call. However, a DSL uses an implicit context.
-
SLAP tells us to keep
all lines of code in a method at the same level of abstraction.
Steve McConnell's Code Complete 2
tells us about this as well, but I don't recall if it had the clever acronym.
-
And finally, Think about Antiobjects.
Quoting Neal, "Antiobjects are the inverse of what we perceive to be the computational objects."
So instead of solving the really hard "foreground" problem, have a look at the "background"
problem (the inverse) to see if it is easier to solve.
As an example, he used PacMan:
Rather than constructing a solution for the "shortest route around the maze," the game has
a "notion of a PacMan 'scent'" for each tile." That way, the ghosts follow the strongest scent.
As with my write-ups on
Scott Davis's Groovy: Greasing the Wheels of Java,
and Stuart Halloway's JavaScript for Ajax Programmers,
Neal gets all the credit for the good stuff here. I'm just reporting from my notes, his mindmap,
and my memory, so any mistakes you find are probably mine. If they aren't, they probably should
have been.
So which strategies have you used? Will you start using any of the above?
Posted by Sam on May 29, 2007 at 07:14 AM UTC - 5 hrs
Often when we make a change to some code, if it is not properly designed, the result is that cascading changes need to be done throughout the application because bugs will ripple through the system. That's one of the ideas behind why we want to have low coupling between classes. We also have unit testing to combat the effects of this, but let's just suppose we haven't written any, and haven't used JUnit Factory to create regression tests.
Given that there is a lot of code out there that isn't quite perfect, wouldn't it be nice to have a tool that could analyze changes and where they would affect other code? I can imagine how such a tool might work, but I haven't heard of one before now (that I recall, anyway).
So the point of it all: I heard something about BMC and IBM teaming up on such a tool (my understanding is that BMC started it, and IBM later joined the project). I'm assuming it'd be in Java, but does anyone have information on this? Can anyone confirm or deny the story I heard?
Last modified on May 29, 2007 at 07:15 AM UTC - 5 hrs
Posted by Sam on May 19, 2007 at 01:25 PM UTC - 5 hrs
Peter Bell's presentation on LightWire
generated some comments I found very interesting and thought provoking.
(Perhaps Peter is not simply into application generation, but comment generation as well.)
The one I find most interesting is brought up by several people whose opinions I value -
Joe Rinehart, Sean Corfield,
Jared Rypka-Hauer, and others during and after the presentation.
That is: what is the distinction between code and data, and specifically, is XML code or data
(assuming there is a difference)?
More...
The first item where I see a distinction that needs to be made is on, "what do we mean when we are talking about
XML?" I see two types - XML the paradigm where you use tags to describe data, and the XML you write - as in,
the concrete tags you put into a file (like, "see that XML?"). We're talking about XML you've written, not
the abstract notion of XML.
The second idea: what is code? What is data? Sean Corfield offers what I would consider to be a concice,
and mostly correct definition: "Code executes, non-code (data) does not execute." To make it correct (rather
than partially so), he adds that (especially in Lisp) code can be data, but data is not code. You see this
code-as-data any time you are using closures or passing code around as data. But taking it a bit further -
your source code is always just data to be passed to a compiler or interpreter, which figures out what the
code means, and does what it has been told to do.
So is XML code? Certainly we see it can be: ColdFusion, MXML, and others are languages where your
source code is written (largely) in XML. But what about the broader issue of having a programmatic
configuration file versus a "data-only" config file?
Is the data in the config file executable? It depends on the purpose behind the data. In the case of data
like
<person>
<name>
Bill
</name>
<height>
4'2"
</height>
</person>
I think (assuming there is nothing strange going on) that it is clearly data. Without knowing anything about the
back end, it seems like we're just creating a data structure. But In the case of
DI (and many others uses for config files),
I see it as giving a command to the DI framework to configure a new bean. In essence, as Peter notes,
we've just substituted one concrete syntax for another.
In the case of XML, we're writing (or using)
a parser to send data to an intepreter we've written that figures out what "real" commands to run based on
what the programmer wrote in the configuration file. We've just created a higher level language than we had before
- it is doing the same thing any other non-machine code language does (and you might even argue
about the machine code comment). In the configuration case,
often it is a DSL (in the DI case specifically, used to describe which objects depend on which other
objects and load them for us).
While we don't often have control structures, there is nothing stopping us from implementing them,
and as Peter also notes, just because a language is not
Turing complete), doesn't mean it is not
a programming language. In the end, I see it as code.
Both approaches are known to have their benefits and drawbacks, and choosing one over the other is largely a matter
of personal taste, size and scope of problem, and problem/solution domain. For me, in the worlds of
JIT compiling and interpreted langages, the programmatic way
of doing things tends to win out - especially with large configurations because I prefer to have
the power of control structures to help me do my job (without having to implement them myself).
On the other hand, going the hard-coded XML route is especially popular in the compiled world, if not
for any other reason than you can change configurations without recompiling your application.
I don't make a distinction between the two on terms of XML is data, while programming (or using an in-language DSL)
in your general-purpose language is code. To me, they are both code, and if either is done incorrectly it will
blow-up your program.
Finally, I'm not sure what value we gain from seeing that code is data (and in many cases config data is code),
other than perhaps a new way of looking at problems which might lead us to find better solutions.
But that isn't provided by the distinction itself, just the fact that we saw it.
Comments, thoughts, questions, and requests for clarifications are welcome and appreciated.
Posted by Sam on Apr 29, 2007 at 10:59 AM UTC - 5 hrs
Robert "Uncle Bob" Martin has put together a post about just what SOA is. It's very easy to understand (especially for web developers / CF programmers, since we're always talking about MVC). So if you've ever wondered "WTF is SOA?" (like me), I'd recommend giving it a read.
Demystify SOA for yourself.
Posted by Sam on Apr 12, 2007 at 07:55 PM UTC - 5 hrs
This post at Worse Than Failure made its rounds today on the Yahoo pragprog group and CFCDev mailing list, and I had a response to one of the emails that I also thought was worth blogging. On pragprog, the discussion was turning into the question, "where do you draw the line on knowing when to eliminate literals?" (not a direct quote) Someone brought up the idea to use YAGNI and DRY as guiding factors: "If you need the number in just one place, just use the number there. YAGNI," and "If you find the number being repeated so that you would have to change
it in several places, then externalise it so that I change in just one place is needed. DRY."
More...
Initially, that struck me as an idea worth exploring, since those are two of my most-used, favorite, and life-saving principles (I mean, try violating them over and over for years and see if you don't feel like dying!). In fact, I use them a lot in determining when to create a new method (I use other heuristics as well, such as readability, which is often the most important reason I create a function ... more here about some reasons for creating methods). But, I thought about it some and came to this conclusion I posted to the pragprog group (which I changed a bit for here to make more sense):
In essence, I'm not comfortable with either of those definitions (DRY or YAGNI) as lines for determining when you are soft or hard coding. Suppose the
following (generalizing to your pleasure -- since I'm having trouble writing it, I'm sure others will have trouble understanding it, as no one would ever (I think) write an application this way)
- it always costs the same to ship each item: 5.00 (units of currency)
- the company only needs to worry about one tax rate: 5%
- the product costs 100 dollars each, and there is only one product.
- customers can buy a variable quantity of this item (called "quantity" below)
- there is a handling charge of 3.14 (units of currency) for each item.
Then your total_cost = (100+5+3.14)*(1.0+5/100)*quantity.
We see 100 twice here. Do we pull it out? What do we name it? oneHundred? That is the only one that makes sense for both cases. Or we
could change it to to get rid of calculating the percentage: total_cost = (100+5+3.14)*(1.05)*quantity.
I'm not even confident I've got the right formula - and I'm the one
making this up. What happens down the line if the state changes the sales
tax? Am I sure I know which number to manipulate? Here, it may look
clear, but it may not be the case in other situations. And then when I
need to do a circleOperation with crappyApproximationOfPi = 3.14 (the
product is cylindrical, and we need to know about how much ribbon we
need to wrap the package when someone selects the gift wrapping option), who gets
the naming rights?
If there is a line that should be drawn (and I'm not convinced there is),
and there exists a place to put it, I don't think the DRY or YAGNI principles have
anything to do with its placement. Readability is the key for me, and
I don't know to what degree that can be quantified.
One in particular I dislike is when people have uncommented literals
like 0x0032aBlahBlahBlah in the code. How (and why) on Earth would you
expect anyone to know what that referred to? Will you remember the next
time you look at it? I wouldn't, but my brain may just be a sieve.
Your thoughts on where to draw the line? (Or not draw it?... Or tell me I'm wrong?)
Posted by Sam on Mar 30, 2007 at 03:12 PM UTC - 5 hrs
One of the great benefits of using a framework (in the general sense) is the freedom in portability it often gives you. Rather than writing your own Ajax routines which would need to handle browser incompatibilities, you can rely on Prototype/ Scriptaculous, AjaxCFC, Spry, or a seemingly infinite number of Ajax frameworks available. We see the same phenomenon in our use of ColdFusion, which uses Java to be cross-platform. And, you can get the benefit for databases by using a framework like Transfer or Reactor or ActiveRecord in Ruby (all also have a slew of other benefits). In general, we're simply seeing an abstracting away of the differences between low-level things so we can think at a much higher level than "oh crap, what if they're using Lynx!" (Ok, I'm doubting any of those Ajax frameworks fully support Lynx =), but you get the picture)
More...
I can honestly say I've never had to build an application that needed to be portable between databases, aside from between different versions by the same vendor (although there was once where it might have been nice, since I used the apparently impossible to find in hosting combination of Microsoft SQL Server and Java). My guess is that unless you're building software which is meant to be deployed on different databases (such as a framework, library, or software you plan to sell over and over again which you won't be hosting), you probably haven't needed to do so either... Or at least, not often enough to let it impact your coding.
So today, I started my quest to turn cfrails into a DB-independent framework (as of today, March 30, 2007 it works only with Microsoft SQL Server). Since one of my goals is to limit the amount of configuration the programmer has to provide to get up and running, simply telling cfrails your database metadata (instead of having it look in the database itself) was out of the question (XML or otherwise). I've looked at using CF's getMetaData(query_result) function in the past, but that doesn't have near the amount of data that would be useful for me.
I figured my best bet would be to drop down into Java and use its DatabaseMetaData interface. This way, I could let Java take care of the differences between DB implementations for me. Sure enough, looking over the docs, it had everything I needed. I went ahead and whipped up some code to use it and test it before trying to put it into Coldfusion. If you're interested, here's most of what I needed (only using the jdbc/odbc bridge). If not, feel free to skip this code:
// at the top of the file, you'll need to import java.sql.*
// and of course, define your class as you normally would.
Connection connection=null;
String driverPrefixURL = "jdbc:odbc:";
String dataSource = "ODBC Datasource Name";
String username = "user";
String password = "password";
String catalog = "which database";
String schema = "dbo";
String tableYouWantColumnsFor = "sometable";
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connection = DriverManager.getConnection(driverPrefixURL+dataSource, username, password);
DatabaseMetaData dbmeta = connection.getMetaData();
ResultSet cols = dbmeta.getColumns(catalog,schema,tableYouWantColumnsFor,"%");
while (cols.next())
{
// this is all going to be unformatted as-is
System.out.print(cols.getString("COLUMN_NAME") + " ");
System.out.print(cols.getString("DATA_TYPE") + " ");
System.out.print(cols.getString("TYPE_NAME") + " ");
System.out.print(cols.getString("COLUMN_SIZE") + " ");
System.out.print(cols.getString("IS_NULLABLE") + " ");
// no clue why these two lines are throwing errors
//System.out.print(cols.getString("COLUMN_DEF") + " ");
//System.out.print(cols.getString("ORDINAL_POSITION") + " ");
System.out.println(); // skip to the next line
}
connection.close();
}
catch (Exception e)
{
e.printStackTrace();
// probably should be in finally block, but didn't worry about it as this was just a test
try
{
connection.close();
}
catch (Exception ex)
{
System.out.println("Could not close connection - probably wasn't open.");
}
}
(if you need more info, see the docs I linked above -- this is just a small sampling of the metadata available for columns in a table)
Sweet! I didn't really need ORDINAL_POSITION since it already ordered the results by it, and COLUMN_DEF (the column's default value) wasn't all that important (well, I could figure out a way to get that later, anyway). But, when I went to put it into ColdFusion, I realized - I have no clue how I might get the information I need to connect- forget about the fact that I'm not sure the average developer (CF or otherwise) will know what a catalog or schema is, or which one they need to access if they do. Who in the world isn't going to have to go through some work to figure out what driver they need and what the connection URL is?
So, I needed a way to let Java know these things through CF. I thought I could get at least some of that information from the CFAdminAPI (or whatever the proper case and spelling is), but I didn't really like that option. I couldn't think of any other option that would allow me to use Java to get the metadata, so I decided I'd have a look at the codebases for Transfer ORM, Reactor, and DataMgr and see how those smart guys did it. I only browsed, but I didn't find anything useful for me trying to use Java to do the trick. So, it was back to writing a DB-specific implementation for each database I wanted to support.
Luckily for me, even though my design doesn't approach the modularity and complexity of Reactor or Transfer (yet... I'm a big fan of YAGNI and haven't had any reason to break out of the simple design I currently have and Transfer is downright confusing for me to quickly browse it and figure out what's going on), it is quite flexible enough for me to do the different implementations and integrate them with ease. To do so, I only need to refactor the current routine that gets the MSSQL Server metadata, and provide a way to figure out which database type it is looking in (each of these only has to happen once). After that, all that remains is to write a query to get the metadata and convert its output (for each implementation) to the interface the rest of my code expects. Not hard by any means, but it would have been cool for maintenance purposes and greater overall simplicity if I could have let Java handle it.
So now that you've gotten this far in this boring post with no conclusion, have you used Java to get the DB metadata in CF (or even outside of CF)? Have you found a way to do it without making the programmer specify the connection URL and driver?
Posted by Sam on Mar 12, 2007 at 08:43 AM UTC - 5 hrs
Yesterday I was working on a little Java program that, when given a table, a "possible" candidate key (which could be composite), and some non-key columns would check to see if the table was in 1st, 2nd, or 3rd normal form(s). One constraint is that this program needs to be procedural in style (or rather, all of my code must reside in the file that contains the main() function).
I started out with the pseudocode programming process in psvm. My listing started to look like:
More...
// validate the input args[]
// extract the table, "possible" candidate key, and non-keys
// validate that there are no duplicate columns in the "possible" candidate key and the non-keys
// validate the table exists
// validate the columns exist for the "possible" candidate key and the non-keys
...
// and so on
At that point, I decided to go ahead and start coding. Now, I wasn't using TDD, since I have no clue how I might go about doing so other than to first code in different classes, and then migrate everything over to main(). In that case, I figured there may very well be more bugs after migrating, rather than less. So, I just started coding.
Once I got down to having to hit the database table to verify it and the columns existed, I got stuck. I didn't know if I needed to test against an actual DBMS using JDBC or if I needed to check against a .CSV file, for example. I stayed stuck and thinking for a while, until I started thinking in objects (or more generally, and importantly, abstract data types). At that point I realized that whatever I did, I'd have to store it some how, and in doing so, I could "fake it" and implement that part later. All I needed was the interface, essentially.
Now, this is a technique you'll often use in OOP and especially if you're following test driven development, but given the nature of the constraints, I didn't think about it immediately.
The point is that even if you aren't programming with objects or using TDD, you can still think about abstraction, and use it to continue coding even when the detailed implementation-level requirements aren't fully specified. I'm not sure this is something I would have thought about when programming purely procedurally, so hopefully it will help someone out, as it certainly did for me.
Thoughts anybody?
Posted by Sam on Mar 06, 2007 at 12:13 PM UTC - 5 hrs
Our second meeting of the UH Code Dojo was just as fun as the first. This time, we decided to switch languages from Java to Ruby. And although we started quite slowly (we came unprepared and undecided on a problem and a language), we pretty much finished the anagram problem.
Now, I mentioned it was slow at first - because we were trying to decide on a problem. I'm guessing we spent about 30-45 minutes just looking over Ruby Quiz and then moving on to Pragmatic Dave's Code Kata. We learned from our experience though, and resolved to determine before-hand what we would do in the future. In any case, we finally decided on anagrams as our problem, and one of us mentioned something to the effect of "I don't know about you all, but I use Java too much at work." Of course, there's not much you can say to an argument like that - Ruby it was!
Since we found ourselves violating YAGNI at the first meeting, we decided to do a little more discussion of the problem before we started coding. One of the initial paths we explored was looping over each letter and generating every possible combination of letters, from 1 to n (n being the length of the input). We then realized that would need a variable number of nested loops, so we moved on to recursion. After that, we explored trying to use yield in conjunction with recursion, in an isolated environment. I don't recall the reasoning behind it, but whatever it was, we were starting to discover that when we passed that fork on the road a few minutes back, we took the path that led to the cannibals. (As a side note, if you're unfamiliar: yield sits in a function, which takes a closure as an argument, and runs the code in the closure -- I think that's a simple way of putting it, anyway).
After smelling the human-stew awaiting us, we backtracked a little and started looking for another idea. Enter idea number two: I'm not sure how to describe it in words, so I'll just show the code now and try to explain it afterwards:
char_count = Array.new(26).fill(0)
dictionary = ['blah', 'lab', 'timmy', 'in', 'hal', 'rude', 'open']
word = "BlAhrina".downcase
word.each_byte { |x| char_count[x - ?a] += 1 }
dictionary.each do |entry|
char_count2 = char_count.clone
innocent = true
entry.each_byte do |letter|
index = letter - ?a
if char_count2[index] > 0
char_count2[index] -= 1
else
innocent = false
break
end
end
puts entry if innocent
end
That's it: quite simple. First we initialize an array with a cell corresponding to each letter of the alphabet. Each cell holds a number, which represents the number of times that letter is used in out input, called word. These cells are set by using the line word.each_byte {...}.
Then for each entry in the dictionary, we do something similar: loop through each letter. If the total count for each letter goes to 0, we have a match (and in our case, simply print it to the standard output device). It's really a simple, elegant solution, and I think we're all glad we didn't go down the painful path of recursion. It would be fairly trivial to add a file which contained a real dictionary, and loop over that instead of each word in our array, but we didn't have one handy (nor did we happen to notice that Dave had provided one). And it would have been just an extra step on top of that to find all the anagrams in the dictionary.
I know this is just a silly little problem that you're not likely to encounter in real life, but it shows how even the simplest of problems can be tough without some thought, and I found it to be great practice. In particular, one problem we had was with trying to use TDD. Although we spent some time looking for tests we could write, and ways to test, and we even wrote an empty test thinking about what to put in there - none of us seemed to find anything to test. Now that we see the solution, it's fairly easy to figure out how to test it, but trying to drive the design with the test was proving fruitless for us. Dave alluded to this on the Kata page:
Apart from having some fun with words, this kata should make you think somewhat about algorithms. The simplest algorithms to find all the anagram combinations may take inordinate amounts of time to do the job. Working though alternatives should help bring the time down by orders of magnitude. To give you a possible point of comparison, I hacked a solution together in 25 lines of Ruby. It runs on the word list from my web site in 1.5s on a 1GHz PPC. It’s also an interesting exercise in testing: can you write unit tests to verify that your code is working correctly before setting it to work on the full dictionary.
I didn't read that before we had started (in fact, it wasn't until we had finished that anyone noticed it), but as you can tell, this exercise performed as promised. Our solution was under 25 lines, and while we didn't test it on his word list, I think our results would have been comparable (in fact, I wouldn't be surprised if we had the same basic solution he did).
Thoughts anybody?
Last modified on Mar 06, 2007 at 12:15 PM UTC - 5 hrs
Posted by Sam on Feb 20, 2007 at 06:34 PM UTC - 5 hrs
I can't remember the last time I used anything resembling a stack or queue (other than what I'm about to blog about). Of course, I've used lists and arrays, but not with the same intent you often see in the use of a typical LIFO or FIFO mechanism. I did have occasion to use the concept in the last couple of updates to cfrails, however, so I thought I'd bring it up in case anyone else came across the same use as I have. Let me explain how I arrived at
the need (which also doubles as a little documentation):
Using cfrails, to make a model you simply go into your project and create a CFC that extends cfrails.model in the model folder. Assuming that file has the same name as a table in your database, you've now got an object (after it is instantiated, of course) with all the basic CRUD methods. The case is similar for the view or controller, where you'd be in the appropriate directory, but name it modelname_view.cfc or modelname_controller.cfc. There is a way to change the name of the table if you don't want the model name to match the table for some reason, but I'll not go over it here, as it's an "undocumented feature" at the moment.
More...
So, now we have a table, let's call it drink. It has columns
id (int, pk, identity), brandname (nvarchar 50), label (nvarchar 50), bottle_date (datetime),
and
description (ntext).
The code currently looks like:
<cfcomponent extends="cfrails.model">
</cfcomponent>
In this case, assuming you have the corresponding controller and view set up, cfrails will show label as "Label" and
bottle_date as "Bottle Date" to the user. description will be capitalized as well. Label will be shown as
an <input type="text"/> and description as a <textarea>.
But while brandname would show up as an input text field too, it will be displayed as "Brandname."
Now, if you had called the column brand_name or brandName, the space would be put in automatically.
That's easy enough to do with new tables, but if you're working on an old one where you had used the convention
numBottles, for instance, it would show up as "Num Bottles," which is not something you want displayed to the user.
Another issue shows up with bottle_date. Since it is a datetime column,
cfrails will display it with a select for each of year, month, day, hour, minute (when I get around to
implementing that). But in this case, you might only care about the month and the year.
How do you solve those two problems? That's where some of the configurations / extended metadata comes in.
One of the goals I have in cfrails is to keep related data together.
I'm not fond of the thought that I might need to have to open an extra file to configure my class. So, if
I want the view to show the name of brandname as "Brand Name," I should be able to do that within
drink_view.cfc (actually, you can hand-code your views in templates if the customization options
don't fit the bill, but I'm not getting into that here either).
What I wanted to be able to do was have my files look like this:
<cfcomponent extends="cfrails.view">
<!--- configuration --->
<cfset setHumanName("brandname", "Brand Name")>
<cfset setFieldType("bottle_date","month/YearDate")>
<!--- some extra functions I need --->
...
</cfcomponent>
(As a side note, you could use the underscore version of those functions as well. Also,
the month/YearDate has not yet been implemented as of Feb. 20, 2007, and is subject to change,
but you get the idea.)
That certainly seems easy enough. But, since the controller has to pass the model into
the view through the view's init() method, that configuration code at the top will be
executed before the view has any knowledge of a field called " brandname."
It was a long time coming, but we've finally reached the point where I needed a stack
(well, the order didn't really matter, so what I implemented wasn't really a stack, but it served
the same purpose).
Instead of running the actual function when it is called, I checked if the component was fully instantiated.
If not, I "stack" each function that needs to be run, along with the supplied parameters.
Then, in the init() method, the stacks are "popped" (it's actually done in a queue fashion, but
it feels odd saying that).
Thoughts?
Last modified on Feb 20, 2007 at 06:36 PM UTC - 5 hrs
Posted by Sam on Feb 10, 2007 at 07:15 PM UTC - 5 hrs
I'm in the middle of reading Steve McConnell's Code Complete 2 ( website, Amazon), and it is chock-full of good advice (no wonder its considered a must-read classic for software developers). There's been plenty in it I've wanted to share and blog about, particularly the parts about design and class construction, and when I find the time, I certainly will. It just so happened that this time, my computer was already on so the barriers to blogging were low.
In any case, I'm in the chapter where he's talking about the "Pseudocode Programming Process." The idea is that you write precise pseudocode in English (or really, I suppose any human language that your audience will be reading in), and in doing so, you get a clear description of the intent behind the method. Of course, you shouldn't write a novel - the statements should be concise and to the point. He specifies that it should be written "at a low enough level that generating code from it will be automatic," but it should be written at the level of intent. Intent here means that you should talk about the meaning, not how the code will be implemented. The added bonus of course, is that you now have useful comments for your code.
More...
I think this fits quite nicely with a link Peter Bell put on his blog not that long ago, Programming by Wishful Thinking. Programming by wishful thinking would be at a higher level, yet lower (as I understand it), but it looks like it would dovetail nicely with the PPP. Its a bit off topic, but to explain why I say higher level, yet lower, I just mean that in PBWT, you'd be writing statements in your language of choice to implement a solution, and then you'd go implement those methods (this also works well in TDD. So at the level of the statement, you're writing code, so it is lower level, but it is higher level in that you are designing a higher level routine that would use these helpers. You'd likely use the PPP in both "levels" if you will, though it would probably be more helpful in the lower ones, since the PBWT should be very self-explanatory.
I used to take this approach (described in the PPP), but stopped for reasons I'm unsure of. I liked it when I did it, but I guess I got out of the habit. But, you can be sure I'll be trying it out again, especially since a lot of the stuff I'm working on lately is really dynamic programming, with little "real-world" objects to think about.
One long quote which prompted this post that I wanted to share comes when McConnell is describing that as part of the process, you will be naming your method. Of course, we know that a method name should be unambiguous, because it might signify a problem (lack of cohesion, in particular) if you cannot name it without the ambiguity. Here's what McConnell had to say:
Naming the routine might seem trivial, but good routine names are one sign of a superior program and they're not easy to come up with. In general, a routine should have a clear, unambiguous name. If you have trouble creating a good name, that usually indicates that the purpose of the routine isn't clear. A vague, wishy-washy name is like a politician on the campaign trail. It sounds as if it's saying something, but when you take a hard look, you can't figure out what it means. If you can make the name clearer, do so. If the wishy-washy name results from a wishy-washy design, pay attention to the warning sign. Back up and improve the design.
Sound advice, no?
PS: I finally upgraded to Firefox 2.0 (I don't know what took me so long, but apparently I was still running 1.0 on this computer) and I'm loving that spell-check feature.
Last modified on Feb 10, 2007 at 07:16 PM UTC - 5 hrs
Posted by Sam on Feb 01, 2007 at 03:02 PM UTC - 5 hrs
On Monday (Jan. 29, 2007) we had our first meeting of the UH Code Dojo,
and it was a lot of fun. Before we started, I was scared that I'd be standing
there programming all by myself, with no input from the other members. But,
my fear was immediately laid to rest. Not only did everyone participate a lot,
one of the members, Matt (I didn't catch his last name), even took over the
typing duties after about 45 minutes. That turned out to be a good thing -
since I still can't type worth a crap on my laptop, and he was much faster than me.
Now, it had only been a couple of months since I last used Java - but it still amazes me
how little time off you need to forget simple things, like "import" for "require." I found
myself having several silly syntax errors for things as small as forgetting the
semicolon at the end of the lines.
Overall, we started with 7 people, and had 5 for most of the meeting. We finished with
four because one person had tons of homework to do. It seemed like those five of us
who stayed were genuinely enjoying ourselves.
In any case, we decided to stick with the original plan of developing a tic-tac-toe game.
You would think that a bunch of computer scientists could develop the whole
game in the two hours we were there. But, you'd have been wrong.
I came away from the meeting learning two main points, which I think illustrate the main
reasons we didn't complete the game:
- YAGNI is your friend
- Writing your tests first, and really checking them is very worthwhile
More...
Both points are things most of us already know, but things you sometimes lose sight
of when working like that all the time, or without paying attention. But, I was
paying attention on Monday, so let me explain how I came to identify those points.
We started by trying to identify a class we could test. This was harder than it looked. We
initially decided on Game. There was a lot of discussion on making it an
abstract class, or an interface, or should we just make it support only tic-tac-toe? After
everyone was convinced that we should first make it concrete, because we would not be
able to test it otherwise, we started trying to figure out what we could test.
So, we wrote a test_hookup() method to check that JUnit was in
place and working.
In the end, we also decided that we would probably want to try to
make it abstract after we tested the functionality, so we could support multiple games.
I think that decision proved to be a bad one - because none of us could figure
out a single peice of functionality that a Game should have.
For me, I think this was due mostly to the fact that I was trying to think of
general functionality, so I only came up
with run(), basically the equivalent of play().
After thinking for a couple of minutes about things we could test, we decided we should
go with something more tangible for us - the Board class. Once we did that,
we never had a problem with what to test next. But, we still ran into problems with
YAGNI. We were arguing about what type of object we should use, so I just said "let's use
Object, then anything will fit and we can be more general when we get
back to Game." This led to trouble later, when we had to have checks for
null all the time. So, we changed it to a string whose value could be "x", "o", or "".
That made life, and the code, a lot simpler.
Those are the two main places where ignoring the YAGNI prinicple hurt us. There are also
a couple of places where not writing our tests first hurt us.
The most obvious one is that we were just looking for a green bar. Because of that, we
actually were running the wrong tests, and some of ours hadn't worked in the first
place. We were running the test from the Game class, not the Board.
It wasn't too hard to fix the ones that were broken though.
Other than that, we spent
a lot of time trying to figure out if an algorithm to determine a winner would work (in
our heads). If we had just written the test first, we could have run it and seen
for sure if the algorithm was going to work correctly.
Overall, the only other thing I would have changed about how we developed would have been to
decide on several tests in advance, so we would have plenty to choose from,
rather than thinking of a test, then immediately implementing it.
For reference, I've posted our code below.
// Board.java
public class Board {
private String[][] _board;
private boolean current_player;
public Board(int row, int col)
{
_board = new String[row][col];
current_player = true;
for (int i = 0 ; i < row ; i++)
{
for (int j = 0 ; j < col ; j++)
{
_board[i][j] = "";
}
}
}
public boolean isEmpty()
{
for (int i=0; i<_board.length; i++)
{
for(int j=0; j<_board[i].length; j++)
{
if (!_board[i][j].equals("")) return false;
}
}
return true;
}
public void setMarkerAtPosition(int row, int col)
{
if (current_player)
_board[row][col] = "x";
else
_board[row][col] = "o";
current_player = !current_player;
}
protected Object getMarkerAtPosition(int row, int col)
{
return _board[row][col];
}
protected boolean isGameOver()
{
return false;
}
public boolean isRowWinner(int row)
{
for (int j = 0 ; j < _board[0].length - 1 ; j++)
{
if (!_board[row][j].equals(_board[row][j + 1]))
{
return false;
}
}
return true;
}
public boolean isColWinner(int col)
{
for (int i = 0 ; i < _board.length -1 ; i++)
{
if (!_board[i][col].equals(_board[i][col]))
{
return false;
}
}
return true;
}
public boolean isDiagWinner()
{
boolean diag_winner1 = true, diag_winner2 = true;
for (int i = 0 ; i < _board.length -1 ; i++)
{
if (!_board[i][i].equals(_board[i+1][i+1]))
{
diag_winner1=false;
}
if (!_board[i][_board[0].length-1-i].equals(
_board[i+1][_board[0].length-1-(i+1)]))
{
diag_winner2=false;
}
}
return diag_winner1||diag_winner2;
}
}
// TestBoard.java
import junit.framework.TestCase;
public class TestBoard extends TestCase {
private Board _b;
public void setUp()
{
_b= new Board(3,3);
}
public void testBoard()
{
assertTrue(_b != null);
assertTrue(_b.isEmpty());
}
public void testSetMarkerAtPosition()
{
_b.setMarkerAtPosition(0, 0);
//assertEquals(_b.getMarkerAtPosition(0,0),"x");
}
public void testIsGameOver()
{
assertFalse(_b.isGameOver());
for (int i = 0 ; i < 3 ; i++)
{
for (int j = 0 ; j < 3 ; j++)
{
_b.setMarkerAtPosition(i,j);
}
}
//assertTrue(_b.isGameOver());
}
public void testIsRowWinner()
{
_b.setMarkerAtPosition(0,0);
_b.setMarkerAtPosition(1,0);
_b.setMarkerAtPosition(0,1);
_b.setMarkerAtPosition(1,1);
_b.setMarkerAtPosition(0,2);
assertTrue(_b.isRowWinner(0));
}
public void testIsColWinner()
{
_b.setMarkerAtPosition(0,0);
_b.setMarkerAtPosition(0,1);
_b.setMarkerAtPosition(1,0);
_b.setMarkerAtPosition(1,1);
_b.setMarkerAtPosition(2,0);
assertTrue(_b.isColWinner(0));
}
public void testIsDiagWinner1()
{
_b.setMarkerAtPosition(0,0);
_b.setMarkerAtPosition(0,1);
_b.setMarkerAtPosition(1,1);
_b.setMarkerAtPosition(0,2);
_b.setMarkerAtPosition(2,2);
assertTrue(_b.isDiagWinner());
}
public void testIsDiagWinner2()
{
_b.setMarkerAtPosition(0,2);
_b.setMarkerAtPosition(0,1);
_b.setMarkerAtPosition(1,1);
_b.setMarkerAtPosition(0,0);
_b.setMarkerAtPosition(2,0);
assertTrue(_b.isDiagWinner());
}
}
As you can see, the board object really didn't do what its name implies. That should have
been game, I think. Also, the three check for winner functions are only temporarily
public - we planned on combining them to check for a winner in just one public function.
Other than that, we also need some negative tests, and obviously to complete the game.
Finally, thanks to everyone who showed up for making
it an enjoyable experience! I can't wait for the next one!
Last modified on Feb 12, 2007 at 10:12 AM UTC - 5 hrs
Posted by Sam on Jan 25, 2007 at 12:18 PM UTC - 5 hrs
We all know (or should know) that it's a good idea to keep your methods short. I've seen it
recommended that they should be more than 7 ± 2 lines ( Update: Although I still have seen it recommended they should be that short, I didn't mean to imply that I haven't seen other recommendations. Steve McConnell's Code Complete 2 says 50, I think, as Peter Bell alluded to in the comments below). The goal, of course, is simply to manage complexity,
so as long as you've accomplished that, you're ok.
I've generally just created methods for three purposes that I can identify (in no particular order):
-
Follow the DRY Principle:
Refactor common code out into one method.
-
Create an interface for a class
-
Reduce complexity: Sometimes, I see some code that is hard to understand or is a long
related block (such as
setDefaultVariablesScope()), so I'll put it into
a method so I don't have to think about it while I'm working on something else.
More...
But sometimes, I think I can go further. And sometimes I do, while others I don't. There is a
common thread between most of these times I feel I could go further: if I could name the block
of code. And the commonality between most of the times I don't extract that block into a method: the code is not especially complex,
and I am too lazy to do it "right now."
So, I wonder, would it be a fairly valid rule to say "if you can name a block of code, put it in a method"?
Of course, you can't simply say "if you can't name it, don't put it in a method," since
it might be hard to think of a good name (or, it may just be that the code is not cohesive). But I think the other way might go a long way to reducing complexity
and making the code more easily understood.
What do you think? Do you follow any guidelines that you've noticed (either intentionally or not)?
Last modified on Apr 12, 2007 at 07:34 PM UTC - 5 hrs
Posted by Sam on Jan 25, 2007 at 11:51 AM UTC - 5 hrs
Looking at the code Joe Rinehart provided in this post on why it's a good idea to use XML configuration in Model-Glue, I think I would probably prefer the XML. (And that's saying something!)
But, I also think that the code as it stands could be better abstracted, so that the end-programmer wouldn't need to create all those objects. One strategy to abstract the complexity out of that could be to allow the programmer to just create a config object, and use that API to create other objects. I haven't thought through that idea completely, but it seems preferable to me on its face. I'm sure there are many other strategies as well.
In the comments to that post, Teddy R. Payne mentioned, "a generation script that auto generates your code and writes XML for you would seem like creating an abstraction on top of an abstraction. It seems that would introduce another layer of complexity."
More...
I'm not convinced there is some arbitrary line where abstraction would increase complexity. In fact, as it is designed to reduce it, I'm not sure you'd be doing it right if it did increase complexity.
I would also point out that besides the abstractions we create in Coldfusion, you might
very well see Coldfusion itself as an abstraction on top of Java, which in turn is an abstraction
of C/C++ (No more pointers!), which in turn is an
abstraction over assembler, which is an abstraction over machine code... and so on and on it goes.
Still further, we might want to abstract the idea of a queue (for instance) from handling it as an array
(or your preferred implementation). But that doesn't mean we wouldn't further abstract it as something else
to keep our domain consistent.
In any case, even though, yes you are adding code to do something you can do without code in
the case of a generation script, I see that as a valid approach to reducing complexity, especially if
you find XML to be generally more complex than scripts. Because once you've abstracted it, you no longer have
to think about it.
Last modified on Feb 12, 2007 at 10:14 AM UTC - 5 hrs
Posted by Sam on Jan 24, 2007 at 08:22 AM UTC - 5 hrs
At times, I like to push the limits of the languages I work in, just to see what I can learn. A while back,
I got the idea of using structs as objects in Coldfusion, since instantiating objects is so slow. Of course,
this would only be useful if you had tons of objects, and if it was substantially quicker than creating
real objects. Anyway, I decided to see what I could come up with.
Unfortunately, I don't think this would be any more efficient (and possibly less),
because I need to do three file operations. But, I thought I'd share what I came up with regardless.
Basically, what happens is that you tell pseudo_object.cfc what object you want to create, and it creates
a fake object that behaves like a real one. It reads the CFC
you tell it you want an object of. It creates a struct, finds the name of the variable you stored it in
(multiple references won't matter, to my knowledge), rewrites the methods to use the scope+name of
that variable, plus an extra this or variables scope to make up for
variables named the same in different scopes within the CFC. Then, it attaches those methods to
the struct and you now have a struct which behaves like
an object. It won't work if you have any code outside of methods, however. Also, another problem may
arise if you have something like this.variables.
Since I'm not entirely sure that made any sense, I'll share the code now:
More...
<cfcomponent name="pseudo_Object">
<cfset variables.varscope = "">
<cfset variables.obj_index = 0>
<cfset this.objects = arrayNew(1)>
<cffunction name="po_init" access="public" output="false">
<cfargument name="varscope" required="true">
<cfset variables.varscope = arguments.varscope>
<cfreturn this>
</cffunction>
<cffunction name="populateObject" output="true" access="public">
<cfargument name="relative_path_to_cfc" required="true">
<cfargument name="object" required="true" hint = "Struct returned from prepareObject()">
<cfset var local = structNew()>
<cfset local.objName = getTheNameOfTheVariableThatContainsMe(arguments.object.pseudo_id)>
<cfset local.newfile = arguments.object.pseudo_id & ".cfm">
<cffile action="read" file="#expandpath(arguments.relative_path_to_cfc)#" variable="local.theCfc">
<cfset local.theCfc = replacenocase(local.theCfc, "variables.", "#local.objName#.variables.", "all")>
<cfset local.theCfc = replacenocase(local.theCfc, "this.", "#local.objName#.", "all")>
<cffile action="write" file="#getdirectoryfrompath(getcurrenttemplatepath())##local.newfile#" output="#local.theCfc#">
<cfinclude template="#local.newfile#">
<cfloop collection="#variables#" item="local.key">
<cfif isCustomFunction(variables[local.key]) and not listfindnocase("po_init,getObject,prepareObject,getTheNameOfTheVariableThatContainsMe",local.key)>
<cfset this.objects[variables.obj_index][local.key] = variables[local.key]>
<cfset structdelete(variables, local.key)>
</cfif>
</cfloop>
<cffile action="delete" file="#getdirectoryfrompath(getcurrenttemplatepath())##local.newfile#">
<cfreturn this.objects[variables.obj_index]>
</cffunction>
<cffunction name="prepareObject" returntype="struct" access="public">
<cfset variables.obj_index = variables.obj_index + 1>
<cfset this.objects[variables.obj_index] = structNew()>
<cfset this.objects[variables.obj_index].pseudo_id = createUUID()>
<cfreturn this.objects[variables.obj_index]>
</cffunction>
<cffunction name="getTheNameOfTheVariableThatContainsMe" output="false" access="private">
<cfargument name="keyToFind">
<cfset var local = structNew()>
<cfset local.result = "">
<cfset local.scopes = arrayNew(1)>
<cfset local.scopes[1] = variables.varscope>
|