Jan 28: Apparently I suck
I was reading an article today on outspokenmedia.com that was written back in April of 2009 and I was pretty impressed with it. I too feel that not only are people using the recession as an excuse for not trying, but also that employers are using it as an excuse to get rid of employees without having anyone put up a fight. "I'm sorry Bob, in this economy we have all had to make cutbacks, as a result we are going to give you 10 minutes to get your stuff together and say goodbye to the job you have had for 5 years." To which Bob replies "I can totally understand, at least you are going to give me my Christmas Bonus before I leave right?" hahaha. Poor little developer, you'd be cute if we weren't embarrassed to look you in the eye.
Lisa Barone goes through a few points that employees and especially contractors should be doing in order to not only stay afloat, but to excel during these crazy times. I feel that I am performing the majority of items that Lisa suggests we do: Learn something new, Work harder than everyone else, Do the leg work, Take risks, and Shut up. I am always learning and I love it so this is not a problem for me. Even now that I am not on a full-time gig I still sit in front of my computer for a minimum of 8 hours a day. Not 8 hours a day watching YouTube, but spent coding. I have written a framework from the ground up using PHP5.3 features such as namespaces, closures/lambdas, __callStatic, and late static binding. I use these features to build next level framework components such as Dependency Injection, Annotations and a Unit of Work maintained ORM layer. A little over 7,000 lines of code in a couple of weeks. I have also tried out what the development work cycle is like with Zend Studio 7.1 and Zend Server CE. I also used those tools to build a brand new Zend Framework application using a modular layout and integrating Doctrine as the ORM layer. Finally I have started to work with GWT and have discovered that it can actually be really fun to work with. There are a few other items that I have been researching and playing with, but long story short, I have used my free time to the max increasing my skill set.
The one thing that I am still sucking at is surrounding myself with fighters. It's hard to surround yourself with anyone when you work from home. I feel that I need to start being more involved in the PHP community and so I have been trying to find a PHP user group in Toronto that I could attend and perhaps speak at. So far, no luck. If any of you know of a group in the GTA please feel free to tell me about it in the comments!
Lisa Barone goes through a few points that employees and especially contractors should be doing in order to not only stay afloat, but to excel during these crazy times. I feel that I am performing the majority of items that Lisa suggests we do: Learn something new, Work harder than everyone else, Do the leg work, Take risks, and Shut up. I am always learning and I love it so this is not a problem for me. Even now that I am not on a full-time gig I still sit in front of my computer for a minimum of 8 hours a day. Not 8 hours a day watching YouTube, but spent coding. I have written a framework from the ground up using PHP5.3 features such as namespaces, closures/lambdas, __callStatic, and late static binding. I use these features to build next level framework components such as Dependency Injection, Annotations and a Unit of Work maintained ORM layer. A little over 7,000 lines of code in a couple of weeks. I have also tried out what the development work cycle is like with Zend Studio 7.1 and Zend Server CE. I also used those tools to build a brand new Zend Framework application using a modular layout and integrating Doctrine as the ORM layer. Finally I have started to work with GWT and have discovered that it can actually be really fun to work with. There are a few other items that I have been researching and playing with, but long story short, I have used my free time to the max increasing my skill set.
The one thing that I am still sucking at is surrounding myself with fighters. It's hard to surround yourself with anyone when you work from home. I feel that I need to start being more involved in the PHP community and so I have been trying to find a PHP user group in Toronto that I could attend and perhaps speak at. So far, no luck. If any of you know of a group in the GTA please feel free to tell me about it in the comments!
Jan 16: Interfaces: You are doing them wrong.
Normally I don't do any sort of Introduction to Object Oriented Programming posts because to be honest, it's been a long time since I read chapter one and thinking in objects comes naturally to me. Today I happened across the Interfaces section of the PHP manual; the examples and comments on that page were so brutal I thought maybe I should do a public service and write something about how to create a clear object model using a couple of rules of thumb.
In my opinion, one of the most misunderstood OOP mechanisms in the PHP world is the Interface. Unfortunately, the manual seems to support my opinion. In the first example we have the class Template implementing the interface iTemplate. What is an iTemplate? The rest of the examples are even worse: class a implements b. No wonder developers struggle with interfaces when the people that literally wrote the book for them don't seem to get it. Even worse are the comments though, class Rain extends Weather and implements Water? What? Maybe if they mean water as in the sense of watering your lawn. Even so, there has got to be a better name for it than that. The way that interfaces are done in the Zend Framework offers a real-world example of bad interface design. Take a look at the following sentence and let's see if you can make sense of what the involved components are:
Zend_Validate_Date is a Zend_Validate_Abstract and can Zend_Validate_Interface
Now to be completely fair, these class names do get ugly as a result of having to bear the weight of pseudo-namespacing, but if your interface is actually called "Interface" you know you have a problem. Let's look at it again but this time we will be using PHP 5.3+ (which you should be using because it's awesome) and Zend Framework 2.0 - which may have namespace support:
Date is an Abstract and can Interface
This does not describe what type of an object the Date is. Is it a calendar date? Is it a fruit that can be dried and old people drink to help them go #2 more regularly? Or maybe it is a meeting involving two or more people in order to determine if their physical proximity elicits a chemical response. Who knows? And don't even get me started on "can Interface"! Maybe that's what happens if there is chemistry between the people on the Date? Now if you are an experienced OOP developer, you will know that Abstract is the home of some common functionality that all child classes share and that the Interface defines the contract that any objects implementing it must fulfill. But it doesn't tell you anything about what type of functionality is common to the children of the abstract or what types of behavior must be implemented by objects implementing the interface.
Designing objects can be fairly easy for the most part if you take the stance that your objects are nouns and your interfaces are adjectives. Until you get better at it, try to add the suffix "able" to indicate that objects implementing it are capable of doing something. Try this version of the Zend Framework validation action:
Date is a Validator and is Validatable
You won't always be able to use the "able" rule of thumb, but going through the process of trying to think of one will help you arrive at a better object model. Take a look at a few more examples:
A Circle is a Shape and is Drawable
A TeddyBear is a Toy and is Huggable
A CardDeck is an ArrayObject and is both Countable and Sortable
So hopefully these examples help give you an idea how easy it is to create good code. As always when it comes to designing objects, your best tool is simply reading the objects out loud. The better your design, the less like Yoda you will sound. Of course I couldn't leave this post without throwing down some code, and since I was most appalled by the Weather examples in the manual comments I thought I would do my own weather example. The real key here is to note that not only do weather conditions such as the amount of clouds and the UV index rating affect what the actual temperature feels like, so can what you wear. In other words, weather can use objects of completely different types to do work because it knows that those objects are capable of doing a particular thing. Read More
In my opinion, one of the most misunderstood OOP mechanisms in the PHP world is the Interface. Unfortunately, the manual seems to support my opinion. In the first example we have the class Template implementing the interface iTemplate. What is an iTemplate? The rest of the examples are even worse: class a implements b. No wonder developers struggle with interfaces when the people that literally wrote the book for them don't seem to get it. Even worse are the comments though, class Rain extends Weather and implements Water? What? Maybe if they mean water as in the sense of watering your lawn. Even so, there has got to be a better name for it than that. The way that interfaces are done in the Zend Framework offers a real-world example of bad interface design. Take a look at the following sentence and let's see if you can make sense of what the involved components are:
Zend_Validate_Date is a Zend_Validate_Abstract and can Zend_Validate_Interface
Now to be completely fair, these class names do get ugly as a result of having to bear the weight of pseudo-namespacing, but if your interface is actually called "Interface" you know you have a problem. Let's look at it again but this time we will be using PHP 5.3+ (which you should be using because it's awesome) and Zend Framework 2.0 - which may have namespace support:
Date is an Abstract and can Interface
This does not describe what type of an object the Date is. Is it a calendar date? Is it a fruit that can be dried and old people drink to help them go #2 more regularly? Or maybe it is a meeting involving two or more people in order to determine if their physical proximity elicits a chemical response. Who knows? And don't even get me started on "can Interface"! Maybe that's what happens if there is chemistry between the people on the Date? Now if you are an experienced OOP developer, you will know that Abstract is the home of some common functionality that all child classes share and that the Interface defines the contract that any objects implementing it must fulfill. But it doesn't tell you anything about what type of functionality is common to the children of the abstract or what types of behavior must be implemented by objects implementing the interface.
Designing objects can be fairly easy for the most part if you take the stance that your objects are nouns and your interfaces are adjectives. Until you get better at it, try to add the suffix "able" to indicate that objects implementing it are capable of doing something. Try this version of the Zend Framework validation action:
Date is a Validator and is Validatable
You won't always be able to use the "able" rule of thumb, but going through the process of trying to think of one will help you arrive at a better object model. Take a look at a few more examples:
A Circle is a Shape and is Drawable
A TeddyBear is a Toy and is Huggable
A CardDeck is an ArrayObject and is both Countable and Sortable
So hopefully these examples help give you an idea how easy it is to create good code. As always when it comes to designing objects, your best tool is simply reading the objects out loud. The better your design, the less like Yoda you will sound. Of course I couldn't leave this post without throwing down some code, and since I was most appalled by the Weather examples in the manual comments I thought I would do my own weather example. The real key here is to note that not only do weather conditions such as the amount of clouds and the UV index rating affect what the actual temperature feels like, so can what you wear. In other words, weather can use objects of completely different types to do work because it knows that those objects are capable of doing a particular thing. Read More
Jan 13: I thought Zend_Soap was going to be easy
But then I tried to use it. I had a project that I needed to respond to SOAP requests and considering the issues I had last time I played with SOAP, I thought I'd try it a different way this time. The Zend framework is always described as being a collection of components that you can wire together in order to get work done. Most of the time, you use it to build a full MVC stack application, but I only needed one component (I thought) in order to get my work done.
Turns out you need a whole bunch of junk in order to be able to use the Zend_Soap component without a full copy of the Zend Framework:
Zend_Exception - I always thought this was a dumb thing in the first place. I use the SPL exceptions for my stuff.
Zend_Loader - What? Why? I required my class and most of the ZF uses requires as well so I am not sure why it wanted the loader
Zend_Registry - I thought maybe to cache the wsdl? But that's cached in /tmp by the underlying PHP soap action
Zend_Uri - Makes sense, the service is a web service after all
Zend_Validate - Have to make sure the Uri is valid I guess
Zend_Soap - duh
In the end, I needed 106 source files in 20 folders to get things done. This is especially crazy when you think that you can get a boiler plate client working by doing this:So you may be asking yourself if the Zend_Soap really needs all of those files. I thought so as well, but to be honest, I am trying to use the component to get a quick start on the service, not to spend an annoyingly long time going through each component only pulling out the exact source files I needed and placing them in their proper place in my library folder. You may also be asking yourself why I would bother in the first place. I'll tell you why, dear reader, it's because I wanted automatic WSDL generation! And it's because of this small desire that my troubles began. Read More
Turns out you need a whole bunch of junk in order to be able to use the Zend_Soap component without a full copy of the Zend Framework:
Zend_Exception - I always thought this was a dumb thing in the first place. I use the SPL exceptions for my stuff.
Zend_Loader - What? Why? I required my class and most of the ZF uses requires as well so I am not sure why it wanted the loader
Zend_Registry - I thought maybe to cache the wsdl? But that's cached in /tmp by the underlying PHP soap action
Zend_Uri - Makes sense, the service is a web service after all
Zend_Validate - Have to make sure the Uri is valid I guess
Zend_Soap - duh
In the end, I needed 106 source files in 20 folders to get things done. This is especially crazy when you think that you can get a boiler plate client working by doing this:So you may be asking yourself if the Zend_Soap really needs all of those files. I thought so as well, but to be honest, I am trying to use the component to get a quick start on the service, not to spend an annoyingly long time going through each component only pulling out the exact source files I needed and placing them in their proper place in my library folder. You may also be asking yourself why I would bother in the first place. I'll tell you why, dear reader, it's because I wanted automatic WSDL generation! And it's because of this small desire that my troubles began. Read More
Jan 8: Naked PHP Framework on Git Hub
So I have been using git for a while now, and I really like it. I really appreciate being able to check in code without having to have a server available and for some reason it just feels a lot more solid than Subversion. Case in point, today I was merging a big branch back into trunk and it was throwing a ton of conflicts on tabbed versus spaced lines... Easy enough to fix but still annoying in the first place. I don't seem to get those kinds of issues with git. *shrug*
So I am just doing a quick post to say that I have put my toy framework up on GitHub so that I can keep track of it, share it with some other people and because I want to be like all of the other cool kids and have some open source junk on GitHub. I feel cooler already!
The framework itself has some interesting features, but as I said, it's mostly just there to experiment on. Check out what's working now:
* MVC stack - Duh, isn't every framework now?
* Highly modular - Everything for a module lives in the module. Configuration, Routes, etc can all be dropped in place as one folder.
* Uses namespaces - The initial reason I started writing this framework was to get namespace experience. Love them.
* Annotations - Allows you to specify stuff in a method/property doc block. @Inject for example.
* Dependency Injection - Uses type hinting and annotations to inject using constructor and/or setter injection. Can use lambdas/closures as object factories. It's sick, trust me.
* ORM - Has a very Django-like object manager. Want to get all BMWs with a model of 750i newer than 2007? It's easy: $cars = Bmw::objects()->filter('model_eq=750i')->filter('year_gt=2007'); Working on implementing single table inheritance right now where all classes for a hierarchy map onto on database table. Uses lots of late static binding.
* Unit of Work - Basically handles persisting Domain Models transparently using the ORM. If you load an object, change it and then delete it. Only the delete is persisted and the changes are ignored. You can read more about it on Martin Fowler's site. It's pretty awesome too.
* Templating - Another port from django. Basically allows for non-php template syntax. I like it, you might not.
Here's what is on the road map to completion:
* Forms - You guessed it. Django port again. Basically each property in a model is a field with validation and all of that junk built in. The forms handle wrangling them to and from the browser.
* Signals - Very similar to how plugins work in the Zend Framework. Instead of being rigidly contained however, you can emit any old signal to the signal bus and it can be ignored or handled by various listeners
* Automatic auditing - The unit of work knows what changes have happened, by adding an @auditable annotation to a class the Unit of Work will track the changes to the object in an auditing table.
* Automatic Sphynx updating - Again the unit of work knows when an object has been updated so if it has the @searchable annotation it will automatically update the sphynx indexes.
* Creation tools. The point is that this framework pretty much wires up everything for you so you just get down to business. Need something similar to Zend_Tool to help peeps out.
* Who knows. After all of that junk is in place... Well, I'll probably have started all over again ... *sigh* A great way to learn a lot about the new PHP 5.3+ features though!
So I am just doing a quick post to say that I have put my toy framework up on GitHub so that I can keep track of it, share it with some other people and because I want to be like all of the other cool kids and have some open source junk on GitHub. I feel cooler already!
The framework itself has some interesting features, but as I said, it's mostly just there to experiment on. Check out what's working now:
* MVC stack - Duh, isn't every framework now?
* Highly modular - Everything for a module lives in the module. Configuration, Routes, etc can all be dropped in place as one folder.
* Uses namespaces - The initial reason I started writing this framework was to get namespace experience. Love them.
* Annotations - Allows you to specify stuff in a method/property doc block. @Inject for example.
* Dependency Injection - Uses type hinting and annotations to inject using constructor and/or setter injection. Can use lambdas/closures as object factories. It's sick, trust me.
* ORM - Has a very Django-like object manager. Want to get all BMWs with a model of 750i newer than 2007? It's easy: $cars = Bmw::objects()->filter('model_eq=750i')->filter('year_gt=2007'); Working on implementing single table inheritance right now where all classes for a hierarchy map onto on database table. Uses lots of late static binding.
* Unit of Work - Basically handles persisting Domain Models transparently using the ORM. If you load an object, change it and then delete it. Only the delete is persisted and the changes are ignored. You can read more about it on Martin Fowler's site. It's pretty awesome too.
* Templating - Another port from django. Basically allows for non-php template syntax. I like it, you might not.
Here's what is on the road map to completion:
* Forms - You guessed it. Django port again. Basically each property in a model is a field with validation and all of that junk built in. The forms handle wrangling them to and from the browser.
* Signals - Very similar to how plugins work in the Zend Framework. Instead of being rigidly contained however, you can emit any old signal to the signal bus and it can be ignored or handled by various listeners
* Automatic auditing - The unit of work knows what changes have happened, by adding an @auditable annotation to a class the Unit of Work will track the changes to the object in an auditing table.
* Automatic Sphynx updating - Again the unit of work knows when an object has been updated so if it has the @searchable annotation it will automatically update the sphynx indexes.
* Creation tools. The point is that this framework pretty much wires up everything for you so you just get down to business. Need something similar to Zend_Tool to help peeps out.
* Who knows. After all of that junk is in place... Well, I'll probably have started all over again ... *sigh* A great way to learn a lot about the new PHP 5.3+ features though!
Dec 29: Dependency Injection: Me likey
So I spent some time over the holidays working on my proof of concept framework. One of the key features I added was something I have always been curious about but never really got around to playing with, Dependency Injection. I thought the idea seemed to be powerful but I never really grokked it until I sat down and started to implement my own DI container. The first thing I did was hit google and search for "PHP Dependency Injection". It turns out there are a lot of frameworks out there now that support some form of injection but I wanted to make sure I was taking the best ideas from all of them and rolling them into my own. I decided to start with Constructor Injection as it is the easiest to implement, but I also wanted to introduce the concept of a context. One of the things we have to contend with frequently in software development is the transition of our code from development environments to production environments, I wanted my framework to knit itself together automatically for whatever environment it was being deployed in. For example, when I am in development I want my logger to use the FirePHP extension and have it log at the debug level. In production I want my logger to be completely silent for all but critical errors which are logged to a remote monitoring server. Most of the time I see this implemented in some form of Environment via an init call As you can see, there are a lot of implementation details that the environment needs to know about in order to get logging together. Even worse is the fact that if you need to get at the logger in code other than in the Environment class itself you need to get it from a registry every time $logger = Zend_Registry::get('logger'); Read More
Dec 28: Book Review: : The Nomadic Developer Last Part
The last part of my review of the book The Nomadic Developer: Surviving and Thriving in the World of Technology Consulting
continues on from my third post by talking about how to Survive once you have your foot in the door of a development firm and then drawing some conclusions about the book itself.
Once you have started working for a company, you obviously want to remain employed with them for as long as possible. The section on surviving had many great information points, for example: People who create profit don't get fired. The real key to achieving this is by being the go-to person for a client. Preferably a big, fat, whale of a client. The odd thing with my current employer is that they wait for me to become available in order to have work done on their core business systems. Time tracking is a cornerstone that all other aspects of the company rest on, from profit projections to project performance metrics. There are several reasons the big Cs want me to work on this project; trust being at the forefront. The people who depend on this information know that I always say what I mean and that when things are on fire I am not going to leave them hanging. Another reason is that I don't bitch and complain (to them) about what a piece of crap the old legacy code is and how hard it is to work with. For me, I take pride in knowing that every source file I open has been made better in some way by the time I close it. Even if it's not actually related to the bug I am fixing. Not having to hear a developer complain is music to the ears of the project manager as well. Much like getting hired in the first place, the surest way to get assigned to a project is for that project's PM to ask for you specifically.
Another interesting point was that you should strive to not be overpaid. How do you know if you are overpaid? Well, you should be bringing in at least 1.4x your salary, if you aren't then your are being paid more than you are worth. This is a key issue, and one many people have not woken up to: for a business, who stays and who goes in tough times is all about money. I actually told a friend of mine Andy who has his own consulting firm EyeMagine Tech, that although I very much enjoyed working for his small shop, he could not afford to hire me at this time. He was a little disappointed at first (as was I) but after a brief time he actually thanked me for helping him see the business sense behind what I had said. It may seem ludicrous that I convinced a potential employer to not hire me, but you better believe that once he is big enough to afford my rate, he will be on the phone calling me for a position. This is the key to everything in consulting: Setting up the relationships that ensure you will be working for years to come. Read More
Once you have started working for a company, you obviously want to remain employed with them for as long as possible. The section on surviving had many great information points, for example: People who create profit don't get fired. The real key to achieving this is by being the go-to person for a client. Preferably a big, fat, whale of a client. The odd thing with my current employer is that they wait for me to become available in order to have work done on their core business systems. Time tracking is a cornerstone that all other aspects of the company rest on, from profit projections to project performance metrics. There are several reasons the big Cs want me to work on this project; trust being at the forefront. The people who depend on this information know that I always say what I mean and that when things are on fire I am not going to leave them hanging. Another reason is that I don't bitch and complain (to them) about what a piece of crap the old legacy code is and how hard it is to work with. For me, I take pride in knowing that every source file I open has been made better in some way by the time I close it. Even if it's not actually related to the bug I am fixing. Not having to hear a developer complain is music to the ears of the project manager as well. Much like getting hired in the first place, the surest way to get assigned to a project is for that project's PM to ask for you specifically.
Another interesting point was that you should strive to not be overpaid. How do you know if you are overpaid? Well, you should be bringing in at least 1.4x your salary, if you aren't then your are being paid more than you are worth. This is a key issue, and one many people have not woken up to: for a business, who stays and who goes in tough times is all about money. I actually told a friend of mine Andy who has his own consulting firm EyeMagine Tech, that although I very much enjoyed working for his small shop, he could not afford to hire me at this time. He was a little disappointed at first (as was I) but after a brief time he actually thanked me for helping him see the business sense behind what I had said. It may seem ludicrous that I convinced a potential employer to not hire me, but you better believe that once he is big enough to afford my rate, he will be on the phone calling me for a position. This is the key to everything in consulting: Setting up the relationships that ensure you will be working for years to come. Read More
Dec 27: Book Review: The Nomadic Developer Part Three
The third part of my review of the book The Nomadic Developer: Surviving and Thriving in the World of Technology Consulting
continues on from my second post but focuses on the chapter that talks about what you need to ask before you join a firm. It may seem weird, but there are actually many reasons you might not want to join an organization. The obvious ones are related to pay and benefits but I found some of the questions were insightful for determining not only the enjoyment that I could experience working there but also the strength of the company for ensuring that I enjoy a long term contract before before having to hunt for another gig.
One of the key issues I find with my current employer is that they are not very transparent with their sales process. I am not sure if it is because they miss so many sales bids or if they are too busy to update everyone but I definitely think that there is a serious lack of information flowing from sales to the rest of the organization. This makes it hard to stay on top of the vision of the company. By knowing what projects a company is pitching for, you know where you should focus your learning efforts to stay on the cutting edge projects in your company. Two of the best questions they recommend you ask in my opinion were "Does the delivery organization work with sales to make sure estimates are realistic?" This is an important one to ask because knowing that you are going to miss your deadline before you even create your first source file is a very stressful situation. Having a sales team estimate blindly can be a real recipe for disaster. The other important question for me was in two parts "How are leads generated? What happens if a lead is generated by a consultant?" No brainer. Without solid leads your contractor butt is history. That being said, if you generate a lead it would be nice to get more than a free lunch and a pat on the back. Read More
One of the key issues I find with my current employer is that they are not very transparent with their sales process. I am not sure if it is because they miss so many sales bids or if they are too busy to update everyone but I definitely think that there is a serious lack of information flowing from sales to the rest of the organization. This makes it hard to stay on top of the vision of the company. By knowing what projects a company is pitching for, you know where you should focus your learning efforts to stay on the cutting edge projects in your company. Two of the best questions they recommend you ask in my opinion were "Does the delivery organization work with sales to make sure estimates are realistic?" This is an important one to ask because knowing that you are going to miss your deadline before you even create your first source file is a very stressful situation. Having a sales team estimate blindly can be a real recipe for disaster. The other important question for me was in two parts "How are leads generated? What happens if a lead is generated by a consultant?" No brainer. Without solid leads your contractor butt is history. That being said, if you generate a lead it would be nice to get more than a free lunch and a pat on the back. Read More
Dec 26: Book Review: The Nomadic Developer Part Two
The second part of my review of the book The Nomadic Developer: Surviving and Thriving in the World of Technology Consulting
continues on from my first post concerns the portions of the book related to getting into a consulting firm.
There is a lot of common sense in the book, and although this chapter does have have a lot of ideas that fall into that vein. "Appearance Matters" and "Be easy to work with" are a perfect example of something that you shouldn't have to be told. There are some definite points that may developers need to remember. "Always be learning" is one of those suggestions that I wish many developers would take to heart. For me, this is not a difficult thing to do as it is the reason I was drawn to software development and technology in general. Things are always changing, and I like to constantly improve my techniques and skills. There are many in this industry though that joined it because of the promise of high-paying jobs. We can definitely earn high rates for what we do, but only if your skills are in demand. Some of the other points that stuck with me from this chapter are:
* Be active in your technical community
* Demonstrate good writing skills
* Develop your network
The last item is one of the main concepts of the book, and to be honest, this is the area that I need the greatest improvement in. This idea only makes sense to me since for most of my life I have gotten job by being personally recommended by someone I know. Let's face it, a good resume can be put together by anyone who has been in the industry for a couple of years. Having a quality person hand your resume into the person in responsible for filling the position is worth more than anyone you could write on the piece of paper.
The next part of this book review is important for knowing what you need to know in order to join a firm.
There is a lot of common sense in the book, and although this chapter does have have a lot of ideas that fall into that vein. "Appearance Matters" and "Be easy to work with" are a perfect example of something that you shouldn't have to be told. There are some definite points that may developers need to remember. "Always be learning" is one of those suggestions that I wish many developers would take to heart. For me, this is not a difficult thing to do as it is the reason I was drawn to software development and technology in general. Things are always changing, and I like to constantly improve my techniques and skills. There are many in this industry though that joined it because of the promise of high-paying jobs. We can definitely earn high rates for what we do, but only if your skills are in demand. Some of the other points that stuck with me from this chapter are:
* Be active in your technical community
* Demonstrate good writing skills
* Develop your network
The last item is one of the main concepts of the book, and to be honest, this is the area that I need the greatest improvement in. This idea only makes sense to me since for most of my life I have gotten job by being personally recommended by someone I know. Let's face it, a good resume can be put together by anyone who has been in the industry for a couple of years. Having a quality person hand your resume into the person in responsible for filling the position is worth more than anyone you could write on the piece of paper.
The next part of this book review is important for knowing what you need to know in order to join a firm.
Dec 15: Book Review: The Nomadic Developer
So I just finished reading The Nomadic Developer: Surviving and Thriving in the World of Technology Consulting
and I thought I'd write up a quick book review on it to get back into the swing of things with my blog. The problem is, once I got started I ended up having a lot to say about this book and about my experiences as a technology consultant. I have decided to break the book review down into the main sections that I found useful from this book. I guess from the introduction you can surmise that I liked the book and that I am going to recommend that you get it. You would not be mistaken, but let me explain why.
Read More
Read More
Dec 15: Agile Development: You're doing it wrong.
I was really annoyed today by a seemingly harmless line in an email: Given the compressed timeline, we adopted an agile approach to the build.
I love it when a firm starts on a project with a crazy immovable deadline that they can't make if they have to waste time planning it and so they decide to use an "agile" development "process". The trouble of course is that they really mean they don't want to do any planning at all - not even a crappy waterfall process. Instead they spray tasks at developers and pray that they there are no scary things that are going to come and bite them in the eleventh hour. A project descending into chaos is usually a sign of a project that is about to fail as panic sets in. Setting out chaotic from the very start sounds like a project I do not want to be on.
Agile development does not mean throwing out all planning, it means forcing the client to choose what is really important to them. I have been on an agile project that basically built an entire website from the graphic artists design document. Then people wonder why no one knows what a particular button is for, and why it's not in everyone's copy of the jpg. Agile development also means making sure that each component is complete before moving on to the next task. I find that developers will sometimes not implement a feature such as sorting a list with the hope that it will be caught in QA and then they will essentially be able to extend your development time into the QA period. You sneaky little developer, you'd be cute if it wasn't for the fact that you are freaking out because you haven't slept in weeks and your family is starting to take the guy that mows your lawn out to do family things because you are never around.
Another key to the agile process is that the team remain flexible to client changes. Pushing code early and often is definitely the cornerstone of this process but if the client comes back after a code push with a whole mess of changes, where exactly is this time going to come from? Sadly it's another reason why you are not going to get to spend the weekend relaxing or bringing in the patio furniture before it snows ... ummm, be right back, I just remembered something I forgot to do. If you aren't the master of time and space then there is simply no way to do more work in the same amount of time which means you need to take weekends and evenings away from developers. This leads to burnout which hurts your team's productivity for the remainder of this project and most likely the next project they are on.
Then people wonder why the agile development process hurts so much. It's simple really, you've never used it before.
I love it when a firm starts on a project with a crazy immovable deadline that they can't make if they have to waste time planning it and so they decide to use an "agile" development "process". The trouble of course is that they really mean they don't want to do any planning at all - not even a crappy waterfall process. Instead they spray tasks at developers and pray that they there are no scary things that are going to come and bite them in the eleventh hour. A project descending into chaos is usually a sign of a project that is about to fail as panic sets in. Setting out chaotic from the very start sounds like a project I do not want to be on.
Agile development does not mean throwing out all planning, it means forcing the client to choose what is really important to them. I have been on an agile project that basically built an entire website from the graphic artists design document. Then people wonder why no one knows what a particular button is for, and why it's not in everyone's copy of the jpg. Agile development also means making sure that each component is complete before moving on to the next task. I find that developers will sometimes not implement a feature such as sorting a list with the hope that it will be caught in QA and then they will essentially be able to extend your development time into the QA period. You sneaky little developer, you'd be cute if it wasn't for the fact that you are freaking out because you haven't slept in weeks and your family is starting to take the guy that mows your lawn out to do family things because you are never around.
Another key to the agile process is that the team remain flexible to client changes. Pushing code early and often is definitely the cornerstone of this process but if the client comes back after a code push with a whole mess of changes, where exactly is this time going to come from? Sadly it's another reason why you are not going to get to spend the weekend relaxing or bringing in the patio furniture before it snows ... ummm, be right back, I just remembered something I forgot to do. If you aren't the master of time and space then there is simply no way to do more work in the same amount of time which means you need to take weekends and evenings away from developers. This leads to burnout which hurts your team's productivity for the remainder of this project and most likely the next project they are on.
Then people wonder why the agile development process hurts so much. It's simple really, you've never used it before.
Feb 20: SOAP Kills
So I am working on a new assignment at work yesterday, should be a pretty simple task I think. I am consuming a SOAP service to get some simple data. Little did I know that it was such a pain to work with .Net when you are using the PHP SOAP extension. I started out with the following WSDL (just a portion that I wanted to work with is listed).
So I write up a quick test script to grab that WSDL and try to run a method on it. It looked something like this:
Easy-peasy, I have working with PHP driven SOAP services a bunch of times before so I could pretty much write that off the top of my head. So I run the code and I get a SoapFault saying that the Content-type is text/xml when it was expecting application/soap+xml. So I Google around and find that I have to specify the soap version 1.2 in order to get the correct Content-type. So I make the following change to $clientOptions:
Nice, that error is fixed. But Now I have another SoapFault: "The SOAP action specified on the message, '', does not match the HTTP SOAP Action. Google again. This time it looks like the PHP soap client is not able to deal with WSHTTP binding and must use the basicHTTP binding. I am not authenticating for this SOAP service, so I am not sure why it even matters. Luckily, the WSDL tells me that I have basicHTTP bindings available. When I try to use the basic data source though, I get another SoapFault "Action /basic is not defined". So then I email the provider of the feed to make sure their basic binding is configured correctly and working. They make some changes and end up dropping the WSHTTP binding completely from the WSDL. Sweet, that error is fixed.
Except that I am getting another SoapFault, fortunately for me I have run across it aready in my little adventure. It is now complaining that the Content-type application/soap+xml is incorrect as it was expecting text/xml. Seriously! So I go and remove the soap_version element from the $clientOptions array. Awesome, that was an easy one to fix!
But now I have another SoapFault: "Object reference not set to an instance of an object.". Google again. This is where things get really annoying. First of all, the SoapFault error message is ridiculously cryptic. I find all kinds of forum posts and comments in the PHP manual about how you are supposed to pass parameters into the action method on your client. Here are some of the different versions that I tried:
No good.
No good.
No good.
No good. Then I tried adding 'classmap' => array('ListByCode', 'ListByCodeRequest') to the $clientOptions array. That didn't work either. Please bear in mind that these changes may appear to be the flapping around of a dying fish, but I assure you that they each involved several minutes of Google research. I will admit though that by this point I was feeling fairly desperate. I decided to go get a copy of soapUI to see if maybe the feed itself wasn't working. I import the WSDL click on LIST and hit run. Bam! Results are returned immediately. I calmly stand up and leave my office before I bring violence upon my computer.
15 minutes pass
Okay, so I return to me desk and decide to look ONLY at the requests I was making and try to get them to match the ones created by soapUI. Here's what soapUI was doing:
And here is what I was producing:
So obviously my parameters where not being passed in. I tried making the following change:
Which results in this request:
Snap! I am getting a different SoapFault saying that param1 is basically an unknown parameter. I proceed to try several versions of nested arrays and finally give up after this one:
You see I am back to the whole "Object reference not set to an instance of an object." SoapFault. It is then that lightening comes down and strikes my brain. This is the final (and working) version of the script:
So, hopefully this little saga will help others that are in danger of having a very long day as I had escape their fate. On a side note, whenever I am doing this kind of research on Google I always wish that there was an option to specify how old the results should be. I have the option Search: [ ] the web [ ] page from Canada, but I'd really like to see [ ] from this month [ ] from this year [ ] any time as options as well. A blog post mentioning PHP and SOAP from 2003 is probably not going to be helpful. Although sometimes I can see the date it was written before I click, not seeing the entry at all would be great.
So I write up a quick test script to grab that WSDL and try to run a method on it. It looked something like this:
Easy-peasy, I have working with PHP driven SOAP services a bunch of times before so I could pretty much write that off the top of my head. So I run the code and I get a SoapFault saying that the Content-type is text/xml when it was expecting application/soap+xml. So I Google around and find that I have to specify the soap version 1.2 in order to get the correct Content-type. So I make the following change to $clientOptions:
Nice, that error is fixed. But Now I have another SoapFault: "The SOAP action specified on the message, '', does not match the HTTP SOAP Action. Google again. This time it looks like the PHP soap client is not able to deal with WSHTTP binding and must use the basicHTTP binding. I am not authenticating for this SOAP service, so I am not sure why it even matters. Luckily, the WSDL tells me that I have basicHTTP bindings available. When I try to use the basic data source though, I get another SoapFault "Action /basic is not defined". So then I email the provider of the feed to make sure their basic binding is configured correctly and working. They make some changes and end up dropping the WSHTTP binding completely from the WSDL. Sweet, that error is fixed.
Except that I am getting another SoapFault, fortunately for me I have run across it aready in my little adventure. It is now complaining that the Content-type application/soap+xml is incorrect as it was expecting text/xml. Seriously! So I go and remove the soap_version element from the $clientOptions array. Awesome, that was an easy one to fix!
But now I have another SoapFault: "Object reference not set to an instance of an object.". Google again. This is where things get really annoying. First of all, the SoapFault error message is ridiculously cryptic. I find all kinds of forum posts and comments in the PHP manual about how you are supposed to pass parameters into the action method on your client. Here are some of the different versions that I tried:
No good.
No good.
No good.
No good. Then I tried adding 'classmap' => array('ListByCode', 'ListByCodeRequest') to the $clientOptions array. That didn't work either. Please bear in mind that these changes may appear to be the flapping around of a dying fish, but I assure you that they each involved several minutes of Google research. I will admit though that by this point I was feeling fairly desperate. I decided to go get a copy of soapUI to see if maybe the feed itself wasn't working. I import the WSDL click on LIST and hit run. Bam! Results are returned immediately. I calmly stand up and leave my office before I bring violence upon my computer.
15 minutes pass
Okay, so I return to me desk and decide to look ONLY at the requests I was making and try to get them to match the ones created by soapUI. Here's what soapUI was doing:
And here is what I was producing:
So obviously my parameters where not being passed in. I tried making the following change:
Which results in this request:
Snap! I am getting a different SoapFault saying that param1 is basically an unknown parameter. I proceed to try several versions of nested arrays and finally give up after this one:
You see I am back to the whole "Object reference not set to an instance of an object." SoapFault. It is then that lightening comes down and strikes my brain. This is the final (and working) version of the script:
So, hopefully this little saga will help others that are in danger of having a very long day as I had escape their fate. On a side note, whenever I am doing this kind of research on Google I always wish that there was an option to specify how old the results should be. I have the option Search: [ ] the web [ ] page from Canada, but I'd really like to see [ ] from this month [ ] from this year [ ] any time as options as well. A blog post mentioning PHP and SOAP from 2003 is probably not going to be helpful. Although sometimes I can see the date it was written before I click, not seeing the entry at all would be great.
Jan 29: Enterprisey PHP
I hear a lot of talk about the use of PHP in the Enterprise, recently there was even a white paper circulated extolling the virtues of PHP and attempting to make the case that PHP should be the language of choice for Enterprise development. Although the paper does bring up some interesting points such as the large number of extensions and the fact that there are a number of bridges for PHP to load native code written in Java and other languages, taking away from these strengths was that there is way too much emphasis placed on how many websites run PHP. Although it is hard to argue with the fact that PHP is greatly suited to web development, that in no way translates into suitability for Enterprise development. No offense to the language, but I find it perfectly understandable that PHP is not often considered a language suitable for Enterprise development.
PHP developers have a love-hate relationship with Java. Many PHP'ers are quick to point out that "PHP is not Java" and yet when one considers some of the most recent waves in PHP interest, they are frequently things that have been around in the Java world for years and are just now being introduced into PHP. A couple of recent examples are the sudden surge in PHPUnit and phpUnderControl. The latter isn't even a fully native PHP solution but en extension of the venerable Cruise Control continuous integration suite. That being said, I use both of those extensively and they form a crucial part of my software development process. Unfortunately, I feel that the adoption of these technologies has started to plateau as the initial wave of blog posts and general buzz have died down. Many PHPers feel that it's just too much effort for what it's worth. This is the key to the whole debate in my opinion. PHP developers put everything behind speed when it comes to development, in fact I often see bad code decisions being made for the benefits of a few CPU cycles. Perhaps it's because the speed of PHP is the primary strength that developers point to when comparing it to other languages that they focus on that feature. Honestly, PHP should be the fastest language on the web, after all it's all it's done for the last decade.
Here's what you should take away from this post:
1) Using PHP does not make you cooler. It should mean that you have chosen the right tool for the job. There is a reason why Zend Studio (from Zend, The PHP Company) is written in Java and not PHP-GTK. Why be just a PHP developer when you can be a programmer with many tools at your disposal.
2) If you want to improve the way PHP is considered in terms of The Enterprise, make your code look like it was written for the Enterprise. Stop considering your software to be something that you will write and throw away. Write it like it's something you will have to maintain for a decade and some day you just might get to.
PHP developers have a love-hate relationship with Java. Many PHP'ers are quick to point out that "PHP is not Java" and yet when one considers some of the most recent waves in PHP interest, they are frequently things that have been around in the Java world for years and are just now being introduced into PHP. A couple of recent examples are the sudden surge in PHPUnit and phpUnderControl. The latter isn't even a fully native PHP solution but en extension of the venerable Cruise Control continuous integration suite. That being said, I use both of those extensively and they form a crucial part of my software development process. Unfortunately, I feel that the adoption of these technologies has started to plateau as the initial wave of blog posts and general buzz have died down. Many PHPers feel that it's just too much effort for what it's worth. This is the key to the whole debate in my opinion. PHP developers put everything behind speed when it comes to development, in fact I often see bad code decisions being made for the benefits of a few CPU cycles. Perhaps it's because the speed of PHP is the primary strength that developers point to when comparing it to other languages that they focus on that feature. Honestly, PHP should be the fastest language on the web, after all it's all it's done for the last decade.
Here's what you should take away from this post:
1) Using PHP does not make you cooler. It should mean that you have chosen the right tool for the job. There is a reason why Zend Studio (from Zend, The PHP Company) is written in Java and not PHP-GTK. Why be just a PHP developer when you can be a programmer with many tools at your disposal.
2) If you want to improve the way PHP is considered in terms of The Enterprise, make your code look like it was written for the Enterprise. Stop considering your software to be something that you will write and throw away. Write it like it's something you will have to maintain for a decade and some day you just might get to.
Jan 16: Deep Simplicity Part 9
So this is the last rule in the Deep Simplicity series. This time we will be talking about rule #9: Don't use Getter/Setters/Properties At first, I was confused about the rule because the main advantage of OOP is that you have the data and the methods that work on that data in the same place. I have often complained about the ridiculous idea that you should just blanket generate getters and setters for every single property. This leads to copy and paste errors and in my opinion a class that is totally bloated with useless methods. This idea is more in keeping with the spirit of rule 9.
The basic idea is that if you just give blanket access to your properties then you are allowing the client code to ignore encapsulation and in doing so you are allowing operations on your object's dirty bits to take place outside of your class. As usual, allowing this to take place is not a good idea. Take a look at this code:
The Order::getTotal knows way too much about the product object. What happens if there are other factors in determining the price after taxes? Are you going to put all of that logic into the getTotal method as well? What happens if you want to calculate the price after taxes without having an order? Code duplication results in multiple places for possible bugs if the spec changes, you have to "remember" where those calculations are done in order to update them all. Encapsulation solves this problem by making one and only one logical place for functionality to reside. It's hard to remember all of the places that code should change if you have never seen it before. Here is the same functionality with no access to the Product's private parts:
Wow, not a single accessor in sight. Once again the code we end up with looks much cleaner than the original. The semantic intent is clear and more importantly, this new method is much more likely to be reused.
In the end these rules are meant to be a means to an end. You can always come up with edge cases and exceptions to the rules where you find that the code you end up with is maybe not as good as it could have been. The point is not that you follow these rules to the letter or die trying; the point is that by thinking about these rules as you write code you will end up with something that is far better than you would have otherwise done. I hope that the examples I have given have shed some light on how simple and understandable your code can become with a little more effort on your part. If you are interested in more of the great ideas from Thoughtworks, make sure you grab The Thoughtworks Anthology.
The basic idea is that if you just give blanket access to your properties then you are allowing the client code to ignore encapsulation and in doing so you are allowing operations on your object's dirty bits to take place outside of your class. As usual, allowing this to take place is not a good idea. Take a look at this code:
The Order::getTotal knows way too much about the product object. What happens if there are other factors in determining the price after taxes? Are you going to put all of that logic into the getTotal method as well? What happens if you want to calculate the price after taxes without having an order? Code duplication results in multiple places for possible bugs if the spec changes, you have to "remember" where those calculations are done in order to update them all. Encapsulation solves this problem by making one and only one logical place for functionality to reside. It's hard to remember all of the places that code should change if you have never seen it before. Here is the same functionality with no access to the Product's private parts:
Wow, not a single accessor in sight. Once again the code we end up with looks much cleaner than the original. The semantic intent is clear and more importantly, this new method is much more likely to be reused.
In the end these rules are meant to be a means to an end. You can always come up with edge cases and exceptions to the rules where you find that the code you end up with is maybe not as good as it could have been. The point is not that you follow these rules to the letter or die trying; the point is that by thinking about these rules as you write code you will end up with something that is far better than you would have otherwise done. I hope that the examples I have given have shed some light on how simple and understandable your code can become with a little more effort on your part. If you are interested in more of the great ideas from Thoughtworks, make sure you grab The Thoughtworks Anthology.
Jan 13: Deep Simplicity Part 8
Okay, back to the rules! Rule #8 is: Use First-Class Collections. Unlike the previous rule, I really like this one. Basically the rule states that any class that contains a collection should have no other instance variables. This is similar to other Deep Simplicity rules in that it makes it clear what the responsibility of the class is.
On top of this, a simple array has very little semantic intent other than the name that a developer gives the variable that contains it. As we have discussed before lazy programmers can take your well-meaning code and pervert it into meaningless spaghetti by simply chopping a few characters off of a variable name. By making the collection a full-blown object in it's own right it also becomes clear where you should put helper code like filtering or applying a function to all members of the collection. Let's take a look at some code:
So, this is some seriously lazy programming. Although there are a few comments, they are very sparing. The short variable names give you no real clue about what the four arrays represent - at least not without having to expend some brain power figuring out what all of this means. I know you may be saying to yourself that this is a poor example, but I assure you, I have seen code like this in production all the time. I think it's a legacy of the PHP developer lifecycle, starting with one-off single page scripts and eventually evolving into more. Let's see the code again with some more comments and better variable naming:
So this example is much more clear in terms of intent. Although you can now determine what each of the arrays is representing, you have to do a lot of parsing in order to determine what is going on. On top of the difficulty in understanding what is going on, there are lots of bug-prone areas where you deal with the fact that arrays are 0 indexed. such as this line:
Now, some of you are saying that getting a random card from an array of cards should be extracted into a method. I agree with you completely! The questions is, where? Should we put all of this in a Game class which has a Game::getRandomCard(array $cards) method? When you are playing "Go Fish" is there some game device that a player passes their hand into to get a random card? I don't think so. If however, you take the advice of this rule and make all of these arrays into first class objects, it becomes quite clear where that method belongs:
I don't know about you, but this last example seems very clear in terms of what's going on. Even if a lazy developer where to come along and started changing the variable names to three or less characters in length, things still make sense with minimal effort because the logic is hidden away in the Collection itself rather than getting in the way of getting work done.
One more rule to go: #9 Don't user any Getters/Setters/Properties
On top of this, a simple array has very little semantic intent other than the name that a developer gives the variable that contains it. As we have discussed before lazy programmers can take your well-meaning code and pervert it into meaningless spaghetti by simply chopping a few characters off of a variable name. By making the collection a full-blown object in it's own right it also becomes clear where you should put helper code like filtering or applying a function to all members of the collection. Let's take a look at some code:
So, this is some seriously lazy programming. Although there are a few comments, they are very sparing. The short variable names give you no real clue about what the four arrays represent - at least not without having to expend some brain power figuring out what all of this means. I know you may be saying to yourself that this is a poor example, but I assure you, I have seen code like this in production all the time. I think it's a legacy of the PHP developer lifecycle, starting with one-off single page scripts and eventually evolving into more. Let's see the code again with some more comments and better variable naming:
So this example is much more clear in terms of intent. Although you can now determine what each of the arrays is representing, you have to do a lot of parsing in order to determine what is going on. On top of the difficulty in understanding what is going on, there are lots of bug-prone areas where you deal with the fact that arrays are 0 indexed. such as this line:
Now, some of you are saying that getting a random card from an array of cards should be extracted into a method. I agree with you completely! The questions is, where? Should we put all of this in a Game class which has a Game::getRandomCard(array $cards) method? When you are playing "Go Fish" is there some game device that a player passes their hand into to get a random card? I don't think so. If however, you take the advice of this rule and make all of these arrays into first class objects, it becomes quite clear where that method belongs:
I don't know about you, but this last example seems very clear in terms of what's going on. Even if a lazy developer where to come along and started changing the variable names to three or less characters in length, things still make sense with minimal effort because the logic is hidden away in the Collection itself rather than getting in the way of getting work done.
One more rule to go: #9 Don't user any Getters/Setters/Properties
Jan 7: Deep Simplicity Part 7
In this entry I am going to talk about rule #7: Don't Use Any Classes with More Than Two Instance Variables. This for me is one of the hardest rules to try to follow. The author of Deep Simplicity states that he is about to release a one hundred thousand line application that strictly follows these rules, so I guess I just suck.
The reason that we can break any object down to have one or two instance variables is because when you have more than two of them, you can usually group two into one object. Adhering to this rule makes for logical code that has a very defined responsibility. This after all is the whole point to these rules; making it so that everything is so simple it requires little to no thought to work on the project. Let's look at some code:
So we have all seen the classic User object, you might be wondering how we can get this object to have one or two instance variables. You can do it by either working your way down by splitting an object into related halves or working your way up by taking any to instance variables and making an object out of them. So let's try it:
So there we have it. The User object was broken down into finer grained objects that can then house the logic specific to those properties. It's pretty sweet when it works out that way, but in the interest of full disclosure I'll admit that I have no idea how to break up the original User class I was going to use for this example:
To me, that is more like the User objects I see all of the time. I can still break it up into Name and ContactInformation. ContactInformation can be broken down into Address and PhoneNumbers, but I can't think of a way to break up this object:
To me, every property in there belongs to an address. Further, I can't think of a way to group any two of these properties into a new object or a way to split this object in half. Can you see a way to split them? Feel free to comment about it.
Next time is rule #8: Use First Class Collections.
The reason that we can break any object down to have one or two instance variables is because when you have more than two of them, you can usually group two into one object. Adhering to this rule makes for logical code that has a very defined responsibility. This after all is the whole point to these rules; making it so that everything is so simple it requires little to no thought to work on the project. Let's look at some code:
So we have all seen the classic User object, you might be wondering how we can get this object to have one or two instance variables. You can do it by either working your way down by splitting an object into related halves or working your way up by taking any to instance variables and making an object out of them. So let's try it:
So there we have it. The User object was broken down into finer grained objects that can then house the logic specific to those properties. It's pretty sweet when it works out that way, but in the interest of full disclosure I'll admit that I have no idea how to break up the original User class I was going to use for this example:
To me, that is more like the User objects I see all of the time. I can still break it up into Name and ContactInformation. ContactInformation can be broken down into Address and PhoneNumbers, but I can't think of a way to break up this object:
To me, every property in there belongs to an address. Further, I can't think of a way to group any two of these properties into a new object or a way to split this object in half. Can you see a way to split them? Feel free to comment about it.
Next time is rule #8: Use First Class Collections.
« previous page
(Page 1 of 3, totaling 37 entries)
next page »