K.I.S.S. the Ruby Challenge — CLI Image Editor
It all started as a challenge. Get it done until Monday as good as you can. I set a goal of spending one productive day on that task. This means ~ 6 hours of coding tops to do the whole thing, as there are always more important things to do than fun.
Most of the time I spent thinking of simplicity and design of ruby programs and this is the main subject of what you are going to read. I decided to blog about this as ruby development is something I deeply enjoy, and probably because of the upcoming euruko2016.org inspired me to share some of the reasons why I love ruby. Let’s go back to the rules of the game.
Long story short: Create a CLI ‘Image’ Editor.
— An image pixel is represented by an upcased character.
— The user should be able to draw in other color (say character) given x and y coordinates.
— User should be able to draw lines vertically and horizontally from left to right and right to left
— Image should be shown/rendered in the console
Simplicity, readability and extensibility. These were my priorities. Keep it simple, stupid.
I really didn’t want to overdo it. Overengineering is as bad as untested or undocumented code. Complexity could scare developers away from refactoring and even reading such code faster than one might think.
So I started with a list of what we definitely need. I was determined to not add a single line of code if it wasn’t necessary.
- For Command Line Interface program, we definitely need CLI ‘client’ that handles I/O.
- Image model, that holds pixels, represented by characters. 2D Array? Matrix?
- Clear way to edit image and add new features. (Do we need to store history?)
- Renderer Interface with basic console renderer that could be easily changed with one that outputs to a file.
This is always the most crucial part. Never underestimate.
|_edit.rb # module that holds all edit actions
|_cli.rb # class to handle user input
|_image.rb # somethin to represent image
|_renderer.rb # renders image in console
runner.rb # runs the whole party
Everything works, couple of specs cover it and basic structure looks right, right?
03:30 Well no.
After I created and tested the image, renderer and basic CLI, it was time for the real deal. Editing. The goal was to keep it easily extensive. Write 1 or 2 short methods if you want to add a new tool.
While keeping it basic with the image and renderer certainly kept the code clean(2d array that holds the pixels), it appears editing the image doesn’t spare typing and the edit.rb grew quickly to more than hundred loc, which is a mental barrier for me. I had to split it up.
|_base.rb # initialize(@image);
|_clear.rb # exec(params)
|_dot.rb # exec(params)
|_vertical_line.rb # exec(params)
|_horizontal_line.rb # exec(params)
cli.rb: Edit::Clear.new(@image).exec # seems legit
Of course I liked this decision for as long as 3,2 seconds as my mind started racing through all other options.
Hey, you want to impress everybody, load these edit commands on runtime!
Oh boy, oh boy!
Should I list them in a .yml, so that commands have descriptions and
all are automatically read in runtime??
No, no, even better! DSL the shit out of the editor, so everybody can write their editors using ‘language’ that differs from ruby most probably in a negative way, but they will love you!
Stick to your plan. Simplify it. Do not overcomplicate. Questions like ‘Do we need this?’, ‘Will we ever need it?’, ‘How can we handle the situation then if we don’t do it now?’ are to be asked. When in doubt always ask them in this sequence. Just don’t let yourself thinking too far away for no reason. Tic-toc. Code.
01:00 Brain, aren’t you glad I didn’t do any of this?
I stuck to my basic initial decision. It all just works. No hacks, no mind-blowing jungle of objects. The menu is just five when cases in a loop? Does the job for now. Not sexy, but still good enough. When (if) it grows, this needs to be changed to an Interpreter of some sort. Pack into a gem? Put on CI? I think you get the point. These are the easy parts and can be done always afterwards. Spend time wisely and produce less quantity.
After the prototype structure works, don’t try to make it more fancy. Quite the opposite — try to make it simpler. Making your code pleasant to read and understand is more often than not achieved by removing a third of it, not adding more.
As always testing properly (meaning the last 10%) will take most of the time, but even with just a several edge-case scenarios we can cover 90% of the logic. Method does something fancy? Spec it! Otherwise don’t bother to test ruby structures and methods.
00:10 Finishing up. I always wanted…
… unicode strings to be treated the same! I decided the last hour is going to be spent on some coolness like trying ruby 2.40 and it’s support for
"мария".upcase() # out of the box. Mind blown.
00:00. git push
I loved how concentrating on achieving simple goal in simple way produces clean and usable code. If there’s something I learned it is bring in a tool only when the way around it is more complex than just using it.
I believe when working on short but concentrated chunks and not spanning over days, people tend to keep their eyes on the goal, know what needs to be done and see more direct solutions. And this is what coding with ruby is about. This is fun.
A week of ruby awesomeness is starting. ❤ or follow for more.