Posted by Sam on Feb 27, 2012 at 07:45 AM UTC - 6 hrs
Here's a 35 minute recording of the presentation which I gave to houstonrb on February 21, 2012. It is a practice run I did before the live presentation, so you won't get the discussion, but hopefully you'll find it useful anyway.
How to avoid becoming a formerly-employed Rails developer standing in line at the OOP Kitchen from Sammy Larbi on Vimeo.
You can find the slides here: Slides for the Rails OOP presentation
There is also reference to a project whose purpose is to eventually be a full-scale demonstration of the techniques: Project for the Rails OOP presentation
Let me know what you think in the comments below.
Updated to use HTML5 player at Vimeo.
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 Mar 01, 2012 at 06:10 AM UTC - 6 hrs
Posted by Sam on Feb 26, 2011 at 06:53 PM UTC - 6 hrs
I'm writing a Client of FooServer which is reliant upon a rather clunky library whose functionality I'd like to encapsulate in a wrapper library to make the usage less clunky.
My first thought is to choose FooServer as the name of what will likely be the most used class in the library. That way, when I want to get some property of the server or tell it to perform some action I can do something like:
FooServer foo = FooServer.findById(12);
string property = foo.property;
foo.performAction();
That seems innocent enough. But my fear is that by calling the class FooServer , it may violate the principle of least surprise because it does not fully implement a Foo server, nor would it be of use in implementing a Foo server.
I also dislike tagging it with "Wrapper" or something of the sort, because reading that in the client code doesn't seem right either.
I know I'm probably over-thinking it, but names are important to me, because they are the start of clean, readable code that does what it says and says what it does. So that's why I come to you, dear reader.
What would you name the class?
Last modified on Feb 26, 2011 at 06:53 PM UTC - 6 hrs
Posted by Sam on Apr 14, 2009 at 12:00 AM UTC - 6 hrs
This is the eight in a
series of answers to
100 Interview Questions for Software Developers.
The list is not intended to be a "one-size-fits-all" list.
Instead, "the key is to ask challenging questions that enable you to distinguish the smart software
developers from the moronic mandrills." Even still, "for most of the questions in this list there are no
right and wrong answers!"
Keeping that in mind, I thought it would be fun for me to provide my off-the-top-of-my-head answers,
as if I had not prepared for the interview at all. Here's that attempt.
Though I hope otherwise, I may fall flat on my face. Be nice, and enjoy (and help out where you can!).
More...
- What kind of tools are important to you for monitoring a product during maintenance?
I rely on logs and profiling tools on occasion. I'm
really interested to hear from the rest of you about this though.
- What is important when updating a product that is in production and is being used?
I'd say it's important not to interrupt service. Surely there must be something else you're
getting at?
- How do you find an error in a large file with code that you cannot step through?
cout, puts, printf, System.Out.print, Console.Out.WriteLine, and ## have all
been useful for me at one time or another.
A good strategy here is to isolate the code that's causing the error by removing code and faking results.
By doing that, you can slowly add code back in until the error reappears. Rewrite that part.
- How can you make sure that changes in code will not affect any other parts of the product?
Regression tests!
- How do you create technical documentation for your products?
Ideally I'd have comments that can be harvested by tools like RDoc or JavaDoc, but times are often
less than ideal.
- What measures have you taken to make your software products more easily maintainable?
See my answers to the questions about technical design.
Also, having unit tests helps handily.
- How can you debug a system in a production environment, while it is being used?
You can read logs if important events are being logged.
Profiling tools exist for this purpose, but I don't have experience with any outside of those for
use with databases.
- Do you know what load balancing is? Can you name different types of load balancing?
One computer acts as the gatekeeper for an array of computers and directs requests to the others
to "balance the load" of the entire system.
I'm not familiar with different types, but just guessing I'd assume they have round-robin and need-based
load balancing. I'd also presume any other scheduling algorithmic scheme could be applied in load
balancing.
I'm more interested to know why this is on the maintenance list of questions. Is it because you've deployed
your application and now you need to scale it with hardware?
- Can you name reasons why maintenance of software is the biggest/most expensive part of an application's life cycle?
One view is that after you write the first line of code, you begin maintenance. But more in-line with the
popular view: it lasts the longest. You may take a month to build a system that will be in production over
several years. During that time, defects are found that need to be fixed, business rules may change, or
new features may be added.
Also, we suck at writing software.
- What is the difference between re-engineering and reverse engineering?
I didn't know this one. I thought and would have responded that re-engineering would be
rebuilding an application with a white box, while reverse engineering would be done through a black box.
According to Wikipedia, who is never wrong,
The reengineering of software was described by Chikofsky and Cross in their 1990 paper, as "The examination and alteration of a system to reconstitute it in a new form". Less formally, reengineering is the modification of a software system that takes place after it has been reverse engineered, generally to add new functionality, or to correct errors.
This entire process is often erroneously referred to as reverse engineering; however, it is more accurate to say that reverse engineering is the initial examination of the system, and reengineering is the subsequent modification.
How would you answer these questions about software maintenance?
Posted by Sam on Feb 18, 2008 at 06:43 AM UTC - 6 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 - 6 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 - 6 hrs
Posted by Sam on Nov 22, 2007 at 12:04 PM UTC - 6 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 - 6 hrs
Posted by Sam on Nov 19, 2007 at 04:32 PM UTC - 6 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 - 6 hrs
Posted by Sam on Aug 23, 2007 at 09:16 PM UTC - 6 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 - 6 hrs
Posted by Sam on Jul 27, 2007 at 03:43 PM UTC - 6 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 - 6 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 - 6 hrs
Posted by Sam on May 19, 2007 at 01:25 PM UTC - 6 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 - 6 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 - 6 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 - 6 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 - 6 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 - 6 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 - 6 hrs
Posted by Sam on Feb 20, 2007 at 06:34 PM UTC - 6 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 - 6 hrs
Posted by Sam on Feb 10, 2007 at 07:15 PM UTC - 6 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 - 6 hrs
Posted by Sam on Feb 01, 2007 at 03:02 PM UTC - 6 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 - 6 hrs
Posted by Sam on Jan 25, 2007 at 12:18 PM UTC - 6 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 - 6 hrs
Posted by Sam on Jan 25, 2007 at 11:51 AM UTC - 6 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 - 6 hrs
Posted by Sam on Jan 24, 2007 at 08:22 AM UTC - 6 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>
<cfset local.scopeNames[1] = "variables" >
<cfset local.scopes[2] = session>
<cfset local.scopeNames[2] = "session" >
<cfset local.scopes[3] = application>
<cfset local.scopeNames[3] = "application" >
<cfloop from="1" to="#arrayLen(local.scopes)#" index="local.i" >
<cfloop list="#structkeylist(local.scopes[local.i])#" index="local.name" >
<cfif isStruct(local.scopes[local.i][local.name]) and
structKeyExists(local.scopes[local.i][local.name],"pseudo_id" ) and
local.scopes[local.i][local.name].pseudo_id eq arguments.keyToFind>
<cfset local.result=local.scopeNames[local.i] & "." & local.name>
<cfbreak>
</cfif>
</cfloop>
</cfloop>
<cfreturn local.result>
</cffunction>
</cfcomponent>
And so we have something to test with, I've made this simple test.cfc:
<cfcomponent name="test" >
<cffunction name="init" >
<cfreturn this>
</cffunction>
<cffunction name="setVar1" >
<cfargument name="value" >
<cfset variables.var1 = arguments.value>
</cffunction>
<cffunction name="getVar1" >
<cfreturn variables.var1>
</cffunction>
<cffunction name="setThisVar1" >
<cfargument name="value" >
<cfset this.var1 = arguments.value>
</cffunction>
<cffunction name="getThisVar1" >
<cfreturn this.var1>
</cffunction>
</cfcomponent>
And finally, here we see how to create a pseudo-object of test.cfc:
<!--- test_pseudo_object.cfm ---->
<cfset pseudoObjCreator = createobject("component" , "pseudo_object" ).po_init(variables)>
<cfset session.pseudoObj = pseudoObjCreator.prepareObject()>
<cfset session.pseudoObj = pseudoObjCreator.populateObject("test.cfc" , session.pseudoObj)>
<cfset session.pseudoObj.setVar1("five" )>
<cfset session.pseudoObj.setThisVar1("this var 1" )>
<cfset pseudoObj2 = pseudoObjCreator.prepareObject()>
<cfset pseudoObj2 = pseudoObjCreator.populateObject("test.cfc" , pseudoObj2)>
<cfset pseudoObj2.setVar1("six" )>
<cfoutput>
#session.pseudoObj.getVar1()#<br/>
#session.pseudoObj.getThisVar1()#<br/>
#pseudoObj2.getVar1()#
</cfoutput>
Now, it was fun to play around with this stuff, but I realize it's probably useless (I mean, I didn't do
any benchmark testing to see if it was faster than object creation, since I had to read and
write to the file system). Further, I'm not at all happy with the design. My main concern is that the
semantics are exposed: a user of this code needs to know they have to call the methods in the order shown
in test_pseudo_object.cfm to have it work. That could be fixed however, if I required that the name of one
of the variables (and its scope) be passed into the CFC via the init() method. Then,
it would return the struct, all in one go. In fact, if I were to ever start using this, I would reimplement
it in that fashion, and it wouldn't be very hard.
Anyway, what do you think of this useless ...
Last modified on Jan 24, 2007 at 08:23 AM UTC - 6 hrs
Posted by Sam on Jan 15, 2007 at 10:23 AM UTC - 6 hrs
Do you find yourself writing more classes in other languages than in Coldfusion? I do.
For instance, I certainly develop more classes in Java than Coldfusion.
Is it due to the fact that I've developed bad habits in CF and haven't yet broken out of them
(i.e., I'm just unable to spot needs for new classes) or is it because CF is so much more high-level
than Java? A third option may be that the performance issues with instantiating
CFCs contribute to me not wanting
to break out?
More...
I think part of it may be due to bad habits and not having the ability to notice a spot where I need
another class. But, I have no evidence of this, since I haven't been able to notice it =).
I do have evidence of the other two options however: CF being so much more high-level than Java is fairly
self-evident: Where I'll often use a façade to interact with a database in Java, in Coldfusion it would
be pretty useless - since there is no need to instantiate a connection, prepare a statement, prepare a
record set, and then close/destroy them all when you don't need them any more. The other,
about the performance hits, I know sometimes stops me from creating more classes. For instance,
I recently used a mixin to achieve some code reuse with a utility method that didn't belong to any of
the classes that used it. Clearly it broke the "is-a" relationship rule, but I didn't
want to incur the cost of instantiating an object just to use
a single, simple method (the application was already seeming to perform slow).
Of course, this is not a zero-sum game. It is certainly due in part to all three. I'd like to think
it is mostly due to the expressiveness of Coldfusion, but I'll always have to wonder about the bad habits.
In Ruby, I've developed a couple of "significant" applications, but those didn't approach
the size of the one's I've done in Coldfusion or Java. It's hard to say for sure, but I'd guess that I'm
writing much fewer classes in Ruby than I would in Java. Again, this is mostly due to Ruby's expressiveness.
In any case, have you noticed yourself writing fewer classes in CF than in other languages? Have you
thought about it? Come to any conclusions?
Last modified on Jan 15, 2007 at 10:23 AM UTC - 6 hrs
Posted by Sam on Jan 11, 2007 at 12:58 PM UTC - 6 hrs
I don't want to turn this into a mouthpiece for the code dojo at University of Houston, but I'm pretty excited about it since we've set the date of our first meeting. We're planning on doing it January 29, 2007 at 7:00 PM. Check the website for more details (such as the room). We have yet to decide on the first problem to solve / topic, but we will have that done by the end of next week. After that, I probably won't post much here about it, or I'll try not to anyway (I realize folks in China, for instance, could probably care less about it).
Last modified on Jan 11, 2007 at 01:04 PM UTC - 6 hrs
Posted by Sam on Jan 09, 2007 at 04:44 PM UTC - 6 hrs
For those that don't know, cfrails is supposed to be a very light framework for obtaining MVC architecture with little to no effort (aside from putting custom methods where they belong). It works such that any changes to your database tables are reflected immediately throughout the application.
For instance, if you change the order of the columns, the order of those fields in the form is changed.
If you change the name of a column or it's data type, the labels for those form fields are changed, and
the validations for that column are also changed, along with the format in which it
is displayed (for example, a money field displays with the local currency,
datetime in the local format, and so forth).
I've also been developing a sort-of DSL for it,
so configuration can be performed quite easily programmatically (not just through the database), and you can follow DRY to the extreme. Further, some of this includes (and will include) custom data types (right now, there are only a couple of custom data types based on default data types).
More...
In a nutshell, the goal is to have such highly customizable "scaffolding" that there really is no scaffolding - all the code is synthesized - or generated "on the fly." Of course, this only gets you so far. For the stuff that really makes your application unique, you'll still have to code that. But you can compose views from others and such, so it's not like related tables have to stay unrelated, but I do want to stress that right now there is no relationship stuff implemented in the ORM.
I've skipped a few "mini-versions" from 0.1.3 to 0.2.0 because there were so many changes that I haven't documented one-by-one. That's just sloppiness on my part. Basically, I started by following Ruby on Rails' example, and taking my own experience about what I find myself doing over and over again. That part is done, except that the ORM needs to be able to auto-load and lazy-load relationships at the programmer's whim. In any case, once I got enough functionality to start using it on my project, I've been developing them in parallel. The problem is, I've fallen back on poor practices, so the code isn't as nice as it could be.
In particular, there aren't any new automated tests after the first couple of releases, which isn't as bad as it might otherwise be, since a lot of the core code was tested in them. But on that note, I haven't run the existing tests in a while, so they may be broken.
Further, since I've been thinking in Ruby and coding in Coldfusion, you'll see a mix of camelCase and under_score notations. My original goal was to provide both for all the public methods, and I still plan to do that (because, since I can't rely on case from all databases for the column names -- or so I think -- I use the under_score notation to tell where to put spaces when displaying the column names). But right now, there is a mix. Finally, the DSL needs a lot more thought put behind it - Right now it is a mix-and-match of variables.setting variables and set_something() methods. Right now it is really ugly, but when I take the time to get some updated documentation up and actually package it as a zip, I should have it cleaned up. In fact, I shouldn't have released this yet, but I was just starting to feel I needed to do something, since so much had be done on it and I hadn't put anything out in quite some time. Besides that, I'm quite excited to be using it - it's been a pain to build, but it's already saved me more time than had I not done anything like it.
In the end, I guess what I'm trying to say is: 1) Don't look at it to learn from. There may be
some good points, but there are bad points too, and 2) Don't rely too heavily on the interfaces.
While I don't anticipate changing them (only adding to them, and not forcing you to set
variables.property ), this is still less than version 1, so I reserve the right
to change the interfaces until then. =)
Other than that, I would love to hear any feedback if you happen to be using it, or need help because the documentation is out of date, or if you tried to use it but couldn't get it to work. You can contact me here. You can find
cfrails at http://cfrails.riaforge.org/ .
Last modified on Jan 09, 2007 at 04:45 PM UTC - 6 hrs
Posted by Sam on Jan 06, 2007 at 09:25 AM UTC - 6 hrs
Thankfully for me (and anyone who is interested in using his closures package), Sean Corfield reponded to my Beauty of Closures post and set me straight on a couple of issues.
First, he confirmed that there was indeed a bug when attempting to use a mapping to use the closures, and that in fact, it wasn't a CF 6.1 issue only.
Next, he dove right into the code. So, for easy reference, here is my original code:
More...
<!--- container.cfc --->
<cfcomponent>
<cfscript>
variables._arr = arrayNew(1 );
variables._curIndex = 0;
// adds an element to the container
function add(value)
{
_curIndex = _curIndex + 1;
_arr[_curIndex]=value;
}
// iterates over the container, letting a closure
// specify what to do at each iteration
function each(closure)
{
closure.name("run" );
for (i=1; i lte _curIndex; i=i+1)
{
closure = closure.bind(value=_arr[i]);
closure.run();
}
}
</cfscript>
</cfcomponent>
His each() method is only slightly different from mine, but significantly so:
// iterates over the container, letting a closure
// specify what to do at each iteration
function each(closure)
{
for (i=1; i lte _curIndex; i=i+1)
{
closure.call(_arr[i]);
}
}
He mentions that I "[name] the method and then repeatedly [bind] the value variable. That doesn't actually do what he thinks." He's right. I was using it incorrectly, and I thought that it was sort of odd to do it like that. In particular, I didn't like having to name the closure. This syntax is much cleaner, and easier to understand. And the best part follows: I had mentioned that, "from my understanding of closures, you should be able to modify 'outer' variables within them. Thus, to be a 'true' closure, outputting beenhere above should show true" in reference to this code:
<!--- closure_test.cfm --->
<cfscript>
cf = createObject("component" ,"org.corfield.closure.ClosureFactory" );
container = createObject("component" ,"container" );
container.add(10 );
container.add(20 );
container.add(30 );
beenhere = false;
c = cf.new("<cfset value = value + 3> <cfoutput> #value#</cfoutput> <cfset beenhere = true> " );
container.each(c);
c = cf.new("<br/> <cfoutput> This container has the value #value# in it</cfoutput> " );
container.each(c);
</cfscript>
<cfoutput>
#beenhere# <!--- outputs false --->
</cfoutput>
Sean shows that in fact, had I used bind() as it was intended, outputting beenhere would have shown true, as expected. Basically, you can use bind(outer=variables) on the closure to get access to it. Then, in the code for the closure, you could use outer.beenhere = true and of course, the value is changed as you would expect. That is close to what I was thinking of when I said my idea would muck up the syntax, but I was thinking of passing it when creating the object. I actually like this a little better, as it is much cleaner than what I envisioned, and it is easier to see what's going on. It would be nice of course, if there was a way to do it without the programmer worrying about it, but I don't think that is possible with CF, since it uses the the same name for (from what I can tell) are two different scopes.
Anyway, thanks again to Sean for cleaning up my mess.
Last modified on Jan 06, 2007 at 09:31 AM UTC - 6 hrs
Posted by Sam on Dec 18, 2006 at 09:24 AM UTC - 6 hrs
The last couple of weeks I've been soliciting teammates and friends of mine to help on starting a code dojo at the University of Houston. Well, we got the go-ahead yesterday from the CougarCS organization, so now we're just trying to plan when we'll have our first meeting. If you go to UH or live around Houston (I don't think we'll be checking IDs or anything), I'd encourage you to come to one of our meetings. You can find more information at CodeDojo.org. Right now, as I said, we don't have a meeting schedule or anything, but you can follow the link to our google group and stay informed that way (of course we will be posting it on the webpage as well).
If you don't live in Houston, but want to start a dojo of your own, we also plan to provide a place for others to post information. We don't have the infrastructure set up yet, but if you contact me, I'll be glad to let you know when we do. Of course, you won't have to have our cheesy logo up there =).
Last modified on Dec 18, 2006 at 09:31 AM UTC - 6 hrs
Posted by Sam on Dec 18, 2006 at 08:50 AM UTC - 6 hrs
There are plenty of uses for closures, but two of the most useful ones I've found
(in general) are when you really want/need to encapsulate something, and when you want to implement
the Template Method pattern.
Actually, implementing the Template Method pattern using closures may be misusing it, or
I may be mischaracterizing it. It's more of like a "generalized" template
method (particularly since we're not following the pattern, but implementing the intent).
I still don't understand all of the Gang of Four patterns, so have mercy on me if I've
gotten it wrong. =) More on all this below.
So as I get ideas from other languages, I always wonder "how can I use that idea in language X."
For instance, I like the way Coldfusion handles queries as a data structure - so I tried to
implement something similar in Java (which, I will eventually get around to posting).
I encountered closures for the first time in Lisp a few years ago in an Artificial Intelligence
course, I'm sure. But it wasn't until more recently in my experience with
Ruby that I started to understand them. Naturally, I thought "could this be done in Coldfusion?"
More...
Luckily for us,
Sean Corfield made a Closures for CF
package available a couple of months ago (as far as I can tell).
A couple of days ago, I gave it a whirl.
The first thing I have to say is that I had to move the CFCs into the same
directory as my closure_test.cfm file because it wasn't able to find the
temporary templates it was generating. Sean let me know it was because of a pre CF 7.0 bug in
expandPath() , which explains why I had trouble with that function in cfrails. Anyway,
I've posted a "fix" for 6.0/6.1 to the comments on his blog, which basically just
replaces expandPath(".") when it is creating a temp file to
use getDirectoryFromPath(getCurrentTemplatePath()) .
After that, it did take a while to understand what was going on, but once I figured it out,
it was pretty cool. So here's my first class of using closures, and what I did in Coldfusion
to implement it: really "encapsulating" something.
Encapsulation is a word you see thrown around quite often. But you don't achieve it by
putting getters and setters everywhere - particularly if you are passing back complex data
structures from a get() method (without first making duplicates, of course).
The idea behind it, as far as I've been able to understand, is to hide implementation
details from code which doesn't need to know it. Not only does this make the client
code need to be less complex, it certainly lowers your coupling as well. However,
if you have a Container (for instance), how can you encapsulate it?
If you return an array representation, you've just let the client code know too much.
If you don't let the client code know too much, how will it ever iterate over the
contents of your Container ?
Certainly an Iterator comes to mind. You could provide that if you wanted to,
complete with next() , previous() and anything else you might need.
But you could also use a closure to allow you to encapsulate iteration over the Container ,
while still allowing you the freedom to do what you wanted within the loop. Here's my CF code:
<!--- container.cfc --->
<cfcomponent>
<cfscript>
variables._arr = arrayNew(1 );
variables._curIndex = 0;
// adds an element to the container
function add(value)
{
_curIndex = _curIndex + 1;
_arr[_curIndex]=value;
}
// iterates over the container, letting a closure
// specify what to do at each iteration
function each(closure)
{
closure.name("run" );
for (i=1; i lte _curIndex; i=i+1)
{
closure = closure.bind(value=_arr[i]);
closure.run();
}
}
</cfscript>
</cfcomponent>
And here's the resulting code that uses Container.each() , and passes a closure to it:
<!--- closure_test.cfm --->
<cfscript>
cf = createObject("component" ,"org.corfield.closure.ClosureFactory" );
container = createObject("component" ,"container" );
container.add(10 );
container.add(20 );
container.add(30 );
beenhere = false;
c = cf.new("<cfset value = value + 3> <cfoutput> #value#</cfoutput> <cfset beenhere = true> " );
container.each(c);
c = cf.new("<br/> <cfoutput> This container has the value #value# in it</cfoutput> " );
container.each(c);
</cfscript>
<cfoutput>
#beenhere# <!--- outputs false --->
</cfoutput>
Now, from my understanding of closures, you should be able to modify "outer" variables within them. Thus,
to be a "true" closure, outputting beenhere above should show true. This
would be relatively easy to do by sending the appropriate scopes to the closure,
but the only ways I can think of to do that would muck up the syntax. Other than that, it is not code
that makes me comfortable, so to speak. The closure_test needs to know that it is expected
to use value as the name of the variable. There may very well be a simple way around this that
doesn't screw up the syntax, but I have yet to spend enough research in figuring it out. In fact, in
Ruby you'd use |value| do_something_with(value) , so it's probably as simple as that, or providing
an extra parameter (as I saw in one of Sean's examples). Perhaps I will
when I run across a need for it (and I'm sure I will be using this in the future).
And now for comparison purposes, I've included the Ruby version that does the same thing:
class Container
def initialize
@arr = []
@curIndex=-1
end
def add(value)
@curIndex = @curIndex+1
@arr[@curIndex] = value
end
def each
cnt = 0
(@curIndex+1).times do
yield @arr[cnt]
cnt += 1
end
end
end
#trial run
container = Container.new
container.add 15
container.add 25
container.add 35
beenhere = false
container.each{|v| v+=1; beenhere=true}
puts beenhere #displays true
This isn't normally how you'd code in Ruby, but I tried to keep the look approximately the same.
In particular, the each() method is already implemented for arrays in Ruby, so there
would certainly be no need to rewrite it.
As you can see, the syntax is a bit cleaner in Ruby than in the CF version. It's so easy to do,
one doesn't need to think about using closures - you just do it when it comes naturally. Even with
all that, I think Sean reached his goal of proving himself wrong (he said
"I was having a discussion about closures with someone recently and
I said they wouldn't make sense for ColdFusion because the syntax would be too ugly.
On Friday evening, I decided to try to prove myself wrong"). I think it might be possible to
improve it a little, but I don't think it would ever become as "pretty" as Ruby's style (unless, of
course, it was built into the language). Because of that, and the effort involved in creating them,
I don't think it will ever become a natural solution we turn to in CF (although, if I started using them
enough, it would eventually become a natural solution). In any case, he certainly did an awesome job,
made the syntax much cleaner than I would have thought possible, and gave us a new tool to solve problems with.
In closing, I'd like to talk a bit about the "other" use for closures I've found, what I called a generalized
Template Method pattern above. Really, this is just a general case of the way encapsulation was furthered
with the each method above. Basically, you are defining the skeleton of an algorithm, and allowing
someone else to define its behavior. I mentioned that it's not really the template method pattern,
since we aren't confining it to subclasses, and we aren't actually following the pattern - we're just implementing
its intent, in a more general way. So basically, you might think of it as a delayed evaluate for letting
some other code customize the behavior of your code that expects a closure. I don't know if I've made
any sense, but I sure hope so (and if not, let me know what I might clarify!).
Finally I'd like to thank Sean for providing this tool to us, and ask everyone, "what uses
have you found for closures, if any?"
Update: Thanks to Brian Rinaldi, Sean Corfield found this post and addressed the problems I encountered. It turns out I was just doing it wrong, so to see it done right, visit his blog. I also responded to a few of the issues raised here.
Last modified on Jan 06, 2007 at 09:33 AM UTC - 6 hrs
Posted by Sam on Nov 19, 2006 at 05:11 PM UTC - 6 hrs
So, I was playing around a little in Ruby and noticed that DateTime is a subclass of Date , yet the method today is
not defined for it. I asked Venkat about it (in an email), he inspected the source code (why didn't I think of that?), and he replied that
the culprit! is line 1261 in date.rb, within DateTime class
class << self; undef_method :today end rescue nil
I thought it was interesting: Certainly this violates LSP, yet certainly a DateTime shouldn't have the method today . So, are there instances where it makes sense to violate LSP, in favor of keeping up with the metaphor?
I wonder why Date isn't a subclass of DateTime , rather than the other way around? At least for this case, it would not have violated LSP. So, I posted about this on the Ruby-Talk mailing list - I'll update you when I hear back from that group.
Update: I just realized if we made Date a child of DateTime , we'd get a similar problem in that we'd be removing the time part from the class. How would you resolve it?
Update 2: After some thought and some help from the Ruby community, I'm not so sure this is a violation of LSP. You see, the method today can only be called on Date , not an object of Date . So therefore, there is no violation when an object of Date is replaced by one of DateTime . Robert Klemme chimed in with this, which of course I addressed above, but he said it much better than I did:
Once can certainly argue whether DateTime *is a* Date or rather *has a* Date. But it is definitively clear that Date *is not* a DateTime simply because it does not provide the same set of information that DateTime provides (date *and* time)
So in the end, it appears as if there is no problem at all. Sorry for being alarmist - I guess I should have tested it first, right? =).
Last modified on Nov 20, 2006 at 07:29 AM UTC - 6 hrs
Posted by Sam on Oct 26, 2006 at 11:42 AM UTC - 6 hrs
Barry Beattie wrote recently on the CFCDev mailing list a question asking, "if it were Java/JSP, would you bother generating great sections of HTML within the java classes instead of leaving it to the JSP to take care of?"
It got me thinking again about my own use of HTML in CFCs.
At first, I thought it was a valid point - of course I would never consider using HTML in a Java class. But, the more I thought about it, the fuzzier the line got. As it turns out, I could go either way on the CFC/tag issue, depending on the specific case.
More...
In Java/JSP, no, I would almost never write the HTML in a class. I may have some small bits in a UI class from time to time, but never more than a single tag or perhaps two. But, it is not for any other reason than writing HTML like the following is completely unreadable and impossible to follow, and more importantly easy to screw up:
String somehtml = "<html>";
somehtml += "<head><title>" + titleOfThisPage + "</title>";
somehtml += "<meta ...></head>";
...
Can you imagine doing an entire page like that? Or even just a table with more than a couple of columns? It's unwieldy.
With CFC's however, you can still keep the HTML in its readable form. In some cases, I prefer to use CFCs to accomplish the goal of reuse, while in others, I prefer to use includes. I find myself preferring CFCs in particular, in cases where there may be slight customizations to a basic display, based on individual client preferences. I can inherit (or aggregate, if it makes more sense) from the base display (cut up into logical chunks) and reuse those parts which make sense, and only rewrite the custom bits. It lets me keep duplicate code to the absolute minimum - something I'm very fond of these days. The inheritance in particular has the extra benefit of allowing me to keep client code unchanged, as long as I've followed Liskov Substitution Principle.
Last modified on Oct 26, 2006 at 11:44 AM UTC - 6 hrs
Posted by Sam on Aug 29, 2006 at 09:27 AM UTC - 6 hrs
Now that we can insert posts, it is possible to update, select, delete, and search for them. To me, any
one of these would be a valid place to go next. However, since I want to keep the database as unchanged
as possible, I'll start with test_deletePost() . This way, as posts are inserted for
testing, we can easily delete them.
Here is the code I wrote in xorblog/cfcs/tests/test_PostEntity:
<cffunction name="test_deletePost" access="public" returnType="void" output="false" >
<cfset var local = structNew()>
<cfset local.newID=_thePostEntity.insertPost(name="blah" , meat="blah" , originalDate="1/1/1900" , author="yoda" )>
<cfset local.wasDeleted = _thePostEntity.deletePost(local.newID)>
<cfset assertTrue(condition=local.wasDeleted, message="The post was not deleted." )>
<cfquery name="local.post" datasource="#_datasource#" >
select id from post where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#local.newID#" >
</cfquery>
<cfset assertEquals(actual = local.post.recordcount, expected = 0)>
</cffunction>
And the corresponding code for deletePost() :
<cffunction name="deletePost" output="false" returntype="boolean" access="public" >
<cfargument name="id" required="true" type="numeric" >
<cfset var local = structNew()>
<cfset local.result = false>
<cftry>
<cfquery name="local.del" datasource="#_datasource#" >
delete from post where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#id#" >
</cfquery>
<cfset local.result=true>
<cfcatch>
</cfcatch>
</cftry>
<cfreturn local.result>
</cffunction>
Originally, I just left the test as asserting that local.wasDeleted was true. However, writing just
enough of deletePost() in xorblog/cfcs/tests/PostEntity to get the test to pass resulted in the
simple line <cfreturn true> . Since that would always pass, I also added a check that
the inserted post no longer existed.
Now that we have some duplicate code, its definitely time to do some refactoring. More on that next time.
(To be continued...)
Posted by Sam on Aug 28, 2006 at 11:46 AM UTC - 6 hrs
We left off after writing the test for the insertPost() method. Now, we're going to make
that test pass by writing the code for it. First you'll need to create PostEntity.cfc in the xorblog/cfcs/src
directory, and make sure to surround it in the proper <cfcomponent> tags.
What follows is that code:
<cffunction name="insertPost" output="false" returntype="numeric" access="public" >
<cfargument name="name" required="true" type="string" >
<cfargument name="meat" required="true" type="string" >
<cfargument name="originalDate" required="true" type="date" >
<cfargument name="author" required="true" type="string" >
<cfset var local = structNew()>
<cftransaction>
<cfquery name="local.ins" datasource="#variables._datasource#" >
insert into post
(name, meat, originalDate, lastModifiedDate, author)
values
(<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#" > ,
<cfqueryparam cfsqltype="cf_sql_longvarchar" value="#arguments.meat#" > ,
<cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.originalDate#" > ,
<cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.originalDate#" > ,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.author#" > ,
</cfquery>
<cfquery name="local.result" datasource="#_datasource#" >
select max(id) as newID from post
where originalDate=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.originalDate#" >
and name=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.name#" >
</cfquery>
</cftransaction>
<cfif local.result.recordcount is 0 >
<cfthrow message="The new post was not properly inserted." >
</cfif>
<cfreturn local.result.newID>
</cffunction>
There isn't really anything special here, unless you are new to Coldfusion. If that's the case, you'll
want to take note of the <cfqueryparam> tag - using it is considered a "best practice" by
most (if not all) experienced Coldfusion developers.
The other item of note is that if you were to run this code by itself,
it still wouldn't work, since we haven't defined variables._datasource . Many developers
would do this in a function called init() that they call each time they create an object. I've
done it as well.
I suppose if you were rigorously following the
YAGNI principle, you might wait until creating the next
method that would use that variable before defining it. I certainly like YAGNI, but my
OCD is not so bad that I won't occasionally allow
my ESP to tell me that I'm going to use something,
even if I don't yet need it. With that said, I try only do it in the most obvious of cases, such as this one.
Now that we've written the code for insertPost() , its time to run the test again. Doing so,
I see that I have two test that run green (this one, and our test_hookup() from earlier.
We've gone red-green, so now it's time to refactor. Unfortunately, I don't see any places to do that
yet, but I think they'll reveal themselves next time when we write
our second test and second method in PostEntity . (To be continued...)
Last modified on Aug 28, 2006 at 11:52 AM UTC - 6 hrs
Posted by Sam on Aug 25, 2006 at 12:34 PM UTC - 6 hrs
So we decided that blog software centers around posts and that for any other feature
to be useful, we'd need them first. Therefore, we'll start with a model component for our posts,
and we'll call it PostEntity . Before I create that file though, I'm going to go back
into my test_PostEntity.cfc file and write a test or two for some functionality that
PostEntity should provide.
Thinking of things we should be able to do regarding the storage of posts, it's easy to identify
at least insert() , update() , and delete() . However,
since you can't update or delete a post that doesn't exist, I figured I'd start with adding a post.
I came up with the following test:
<cffunction name="test_insertPost" access="public" returntype="void" output="false" >
<cfset var local = structNew()>
<cfset local.nameOfPost = "My Test Post" & left(createUUID(),8 )>
<cfset local.meatOfPost = "The meat of the post is that this is a test." & left(createUUID(),8 )>
<cfset local.dateOfPost = now()>
<cfset local.author = "Sam #createUUID()#" >
<cfset local.newID=_thePostEntity.insertPost(name=local.nameOfPost, meat=local.meatOfPost, originalDate=local.dateOfPost, lastModifiedDate=local.dateOfPost, author=local.author)>
<cfquery name="local.post" datasource="#variables._datasource#" >
select name, meat, originalDate, author
from post
where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#local.newID#" >
</cfquery>
<cfset assertEquals(actual=local.post.name, expected=local.nameOfPost)>
<cfset assertEquals(actual=local.post.meat, expected=local.meatOfPost)>
<cfset assertEquals(actual=local.post.author, expected=local.author)>
<!--- dateCompare isn't working correctly, so we are testing each datepart --->
<cfset assertEquals(actual=month(local.post.originalDate), expected=month(local.dateOfPost))>
<cfset assertEquals(actual=day(local.post.originalDate), expected=day(local.dateOfPost))>
<cfset assertEquals(actual=year(local.post.originalDate), expected=year(local.dateOfPost))>
<cfset assertEquals(actual=hour(local.post.originalDate), expected=hour(local.dateOfPost))>
<cfset assertEquals(actual=minute(local.post.originalDate), expected=minute(local.dateOfPost))>
<cfset assertEquals(actual=second(local.post.originalDate), expected=second(local.dateOfPost))>
<!--- clean up --->
<cfquery datasource="#_datasource#" >
delete from post where id = #local.newID#
</cfquery>
</cffunction>
You'll notice I used a UUID as part of the data. There's no real point to it, I suppose. I just wanted to have
different data each time, and thought this would be a good way to achieve that.
You should also be uncomfortable about the comment saying dateCompare isn't working - I am anyway. It doesn't
always fail, but occasionally it does, and for reasons I can't figure out, CFUnit isn't reporting why. For
now, so I can move on, I'm assuming it is a bug in CFUnit. Since I can test each date part that is
important to me individually and be sure the dates are the same if they all match, I don't feel
too bad.
Another thing to note is the use of the var local . By default, any variables created
are available everywhere, so to keep them local to a function, you need to use the var keyword.
I like to just create a struct called local and put all the local variables
in there - it just makes things easier.
Finally, some people might not like the length of that test. Right now, I don't either, but we'll see
what we can do about that later. Others may also object to using more than one assertion per test. I don't
mind it so much in this case since we really are only testing one thing. If you like, you could also
create a struct out of each and write a UDF like
structCompare() and do the assertion that way. I haven't tested this one personally, but
there is one available at cflib.
In either case, I don't see much difference, other than one way I have to write more code than I need.
Now I run the test file we created and find that, as expected, the test still fails. Besides the fact that
we don't even have a PostEntity.cfc, we haven't yet instantiated an object of that type, nor have
we defined _datasource and the like. Let's do that in the setUp() method.
<cffunction name="setUp" access="public" returntype="void" output="false" >
<cfset variables._datasource="xorblog" >
<cfset variables.pathToXorblog = "domains.xorblog" >
<cfset variables._thePostEntity = createObject("component" , "#variables.pathToXorblog#cfcs.src.PostEntity" ).init(datasource=_datasource)>
</cffunction>
Now our tests still fail, because we have no code or database. So create the datasource and
database with columns as needed:
id (int, primary key, autonumber)
name (nvarchar 50)
meat (ntext)
originalDate (datetime)
lastModifiedDate (datetime)
author (nvarchar 50)
Next time, we'll start coding and get our first green test. (To be continued...)
Last modified on Aug 29, 2006 at 08:38 AM UTC - 6 hrs
Posted by Sam on Aug 23, 2006 at 10:35 AM UTC - 6 hrs
I've recently converted to using CFEclipse (whose website is
down at the time of posting). I downloaded it sometime in September or October 2005
to use on my new laptop since I didn't have a license for Dreamweaver.
At the time, I only had 256 MB RAM in the laptop (now its 2 GB), so I quickly threw it away (CFEclipse,
not the laptop) and started using Notepad for when I had to edit CF files, and generally
shied away from doing any Coldfusion development on that machine.
Since that time, on my other home computer and the one at work, I've been using
Eclipse for Java development (and more recently, the Ruby
and RADRails plugins for Ruby and Rails development). So in the last couple of weeks I'd say,
Dreamweaver has started to annoy me.
I was still using the first Dreamweaver MX version, so that might be the problem, but when doing anything
with the file system it started to hang on me. And, since I had been using Eclipse more consistently
for other languages, it was even worse because I had a constant reminder of how crappy Dreamweaver was.
To be fair, I don't know if they've made the newer versions any better, but I'm not planning to try it unless someone tells
me I just have to try it out.
In any case, because I was so sick of it, I got CFEclipse up and running. And is it ever awesome.
The first thing I love about it is the Methods View, among other things listed at
ColdFusion Developer's Journal. But
that's not all! I've discovered two (probably unintended) "features" it offers:
- When working on large files (I first noticed it on about one about 40 kilobytes), it gets slooooooow. Slow like "you could type 4 or 5 lines of code before it gets done with the first line" slow.
- When working on even larger CFCs, the Methods View stops showing the methods and appears as it does in a .CFM file
So why is this good? Because if you are ever working on CFCs that large, your components quite likely
have more than one responsibility. The annoyance is enough for you to hunt down the extras, pull them
out, and make new components out of them. Design by Annoyance - whatever works, right?
And if you're thinking that its machine related, I'm on a 2.8 GHz CPU with 1 GB of RAM. Not bleeding edge,
but certainly adequate.
Last modified on Aug 23, 2006 at 10:37 AM UTC - 6 hrs
Posted by Sam on Aug 23, 2006 at 09:41 AM UTC - 6 hrs
... and having no automated tests.
Do you ever have those "what the $!*&# was I thinking?" moments? I had one this morning
when working on some legacy code. My guess is that it was probably written six or more years ago,
though someone touched it as late as two years ago. I guess technically it isn't legacy since it is still supported (I'm fixing it aren't I?), but you get the idea.
You see, it was noticed that an order from Portugal to be shipped to California was showing the
customer that they would be charged sales tax, but it was not charging the tax, nor was it showing on the receipt. Can you believe that in all this time, not one customer has ever had that happen?
(Me either)
So, I went to inspect the source to see what the problem was. Only it took me probably half an hour
to find the problem. Why? Because obviously it's best if you can calculate the same thing in
as many different places as possible.
And I'm not talking about something like having tax=calculateTax(someArgs) in one place and taxAmount=calculateTax(someArgs) in another because I forgot what I named the
first variable and I was too lazy to scroll up 10,000 lines to see what it was. I'm talking
"loop through the order to total it, check if the shipping state and billing state are both California, and if so apply tax calculation and add the result to the order total." In three different spots between two different files.
And the best part? As you can tell from the fact the receipts were saying one thing and the web page another, they didn't even follow the same rules. So, the first thing to do was to figure out what
the correct rule was, and then implement it in all three spots.
Just kidding ... I put the code into one spot and reused it.
Last modified on Aug 23, 2006 at 09:44 AM UTC - 6 hrs
Posted by Sam on Aug 22, 2006 at 08:54 AM UTC - 6 hrs
I know there is a general disdain for using CFCs to produce the view layer in applications, but I wanted to take the time to explain when I use it: in situations where several customers will be using the same application (hosted on the same server as well).
In my more inept days, long before CFCs were introduced, I just deployed different versions of the same application to the different directories that housed each client's specific version of the application. This was nice because it allowed customization, but of course it became a giant pain having to fix the same bugs in several different places. The same was true if there was an improvement which would benefit everyone.
Eventually, (still before CFCs) I had the idea to just keep a common code base and <cfinclude> the needed files in each client's own directory. I even built the application(s) such that they would <cfinclude> some custom code in spots that required customer-specific computations or outputs. As you might expect, it wasn't long before customers started asking for changes which couldn't be done in the "customer-specific" includes very easily. It became much easier just to write
<cfif customerID is theCustomerRequestingThisChange>
... do something ...
<cfelse>
... do something for everyone else ...
</cfif>
in the middle of the common code rather than do it correctly. Because of the problems with the different versions I mentioned above, I was very reluctant to "branch" clients into differing code bases - so the code became littered with <cfif> 's and got to be a nightmare to maintain.
Finally, along came CFCs. I could have a component and aggregate it into another or inherit from it in another, and only make the changes I needed to those methods which needed it for those clients who needed it. A silver bullet (almost) for all my problems! The werecode was killed!
But, everywhere I look I see everyone telling me not to use HTML in CFCs! So what do you do in the situation where one client's form
has to differ slightly from the common code? For instance, you might have a form that has a name section, a contact information section, an address section (or two), and perhaps more. Suppose one client wants to track some other information specific to them, and they want it placed just below the name section and just above the contact information. Do you rewrite the form for that client? While you would be following "best practices," this can lead to similar situations as described above.
I've been placing the common code in CFCs - and with great results. Each section of the form can be its own function, to be called in a main displayForm() function. Then, if a client needs a new section, I simply have to rewrite displayForm() and write the new part of the form. If they need to change the order of some fields within a section, I only have to rewrite that section. Its really helped me cut down on duplicate code and helped me follow the DRY principle.
What do you do in similar situations?
Last modified on Aug 22, 2006 at 09:06 AM UTC - 6 hrs
Posted by Sam on Aug 20, 2006 at 10:38 AM UTC - 6 hrs
Since I wanted to start this blog, I thought it would be good practice to write the software that runs it
using test-driven development. I've used a bit of TDD recently for additions to existing applications, but
I've not yet started writing an application using it from beginning to end. I'm getting sick of
eating Italian microwaveable dinners when I have to maintain code. This is my chance to eat something else.
So, without further ado, we'll jump right in.
The first thing I did of course, was to create my directory structure. For the time being, we have:
xorblog/cfcs/src
and
xorblog/cfcs/tests
I like to keep the tests separate from the source. I don't have a reason behind it, other than it helps
keep me a bit organized.
Next, I thought about what a blog needs. We want to deliver items that have the highest business value first,
and move on to things that are lower on the value scale later. In doing this, we get a working application
sooner rather than later, and hence the blog can be used at the earliest possible moment in its development.
With that in mind, we probably shouldn't start with things like Comments or functionality
that lets us get included in places like Technorati. Since
you need content to make anything else useful, I thought I'd start with that. Indeed, the Post is
the core part of a blog. Therefore, the first thing I did was create test_PostEntity.cfc under
xorblog/cfcs/tests.
Now, I'm using CFUnit for my tests, and this assumes you already have it set up. If you need help on
that, you can visit CFUnit on SourceForge.
The first thing I do in test_PostEntity.cfc is write test_hookup() ,
to make sure everything is working:
<cfcomponent extends="net.sourceforge.cfunit.framework.TestCase" output="false" name="test_PostEntity" >
<cffunction name="test_hookup" access="public" returntype="void" output="false" >
<cfset assertEquals(expected=4, actual=2+2)>
</cffunction>
</cfcomponent>
Next, we need a way to see the status of and run our tests. For this we have test_runner.cfm, which
for the most part just copies what you'll find at the CFUnit site linked above:
<cfset testClasses = ArrayNew(1 )>
<cfset ArrayAppend(testClasses, "domains.xorblog.cfcs.tests.test_PostEntity" )>
<!--- Add as many test classes as you would like to the array --->
<cfset suite = CreateObject("component" , "globalcomponents.net.sourceforge.cfunit.framework.TestSuite" ).init( testClasses )>
<cfoutput>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >
<html>
<head>
<title> Unit Tests for xorBlog</title>
</head>
<body>
<h1> xorBlog Unit Tests</h1>
<cfscript>
createobject("component" , "globalcomponents.net.sourceforge.cfunit.framework.TestRunner" ).run(suite,'');
</cfscript>
</body>
</html>
</cfoutput>
Finally, we run that page in a browser to make sure the test runs green - and it does.
Now that we have our test environment set up, we can start writing tests for our PostEntity
that doesn't yet exist. (To be continued...)
Last modified on Aug 20, 2006 at 10:49 AM UTC - 6 hrs
|
Me
|