Model View Controller Rewrite (JavaScript)
I think it's worthwhile rewriting large amounts of code if it adds significant advantage such as cleaner and more maintainable codebase. I have taken some screenshots along the way to document how I do a rewrite.
The contextIn 2006, when I started Todoist, MochiKit was one of the better JavaScript libraries. Today MochiKit is largely forgotten and the state of the art JavaScript libraries are backbone.js, spine.js or ember.js. All of these newer libraries use the Model-View-Controller (MVC) pattern, a pattern I have written about in the past: Model View Controller rewrite in JavaScript/CoffeeScriptI will now show a series of screenshots of what the problem is and how I find a solution that makes the code simpler and more maintainable. Each project in Todoist has a counter that shows how many tasks the project holds. In the old codebase there's one creator and 7 (yes 7!) update functions:
I could rewrite this to use fewer update functions or even just one, but I could also go an extra mile to ensure that such blunders do not happen in the future. The better way to do this is to use the MVC pattern: the basic idea is that we create a counter that autoupdates itself each time the model changes. I looked at how modern libraries handled this (I was largely inspired by backbone.js ) and I began scribbling down how an API could look like:
I did not want to use backbone.js directly as I don't like the way routes work or that it's dependent on underscore (adding backbone.js and underscore would result in 2000 lines of extra code). I also think I can learn more by implementing stuff myself and I can do a customized solution that works well with my existing code. After this step I began to implement a test to see if my API made sense:
I tried to use the new code for the counter (I had two solutions, either binding it to a project object or using ProjectsModel). I tried to use them both throughout some code to see how they would behave in different contexts:
Then I set these solutions against each other - - reflecting on pros and cons of each solution:
The solution I ended up with was following:
Would love to hear about how you attack a rewrite. Happy hacking! Hiring CoffeeScript and Python programmers
We are expanding the team at Todoist and Wedoist with CoffeeScript and Python programmers.
Some of our stats:
Join us either freelance or full-time and work on something that makes the world more productive. Send your resume to amix@amix.dk, be sure to include some code you are proud of (or a link to your GitHub/BitBucket profile). Get 50% off Todoist Premium, for a limited time only
For the next 72 hours only, Todoist Premium is 50% off!
Want to become more productive in 2012? Upgrade for $14/year at todoist.com/premiumPromotion. Todoist is espeically powerful with browser plugins (that integrate nicely wtih Gmail). Check out: vimgrep: Searching through multiple file extensions
Here's a neat trick you can do with vimgrep to search through multiple file extensions (for example, finding a reference in all .js and .coffee files).
To find Users in just .js files recursively you would do: :vimgrep /Users/ **/*.js To find Users in .js and .coffee files recursively you would do: :vimgrep /Users/ **/*.js **/*.coffee This is super useful when you are refactoring code. I am unsure if Ack.vim can do something similar. If you know, please leave it as a comment! You can check out some of my other Vim tips here. Todoist and Wedoist frontpage redesigns
When I started Todoist in 2007 I didn't know much about design. I didn't think much about it and I mostly cared about functional products that solved problems.
Over the years something has changed in me and I am currently obsessed with design. I also think design applies to everything - - from clothing, to code, to food. And design isn't only about how it looks like, but also how it works. I think my change has been fueled by Apple's amazing comeback. One of Apple's biggest differentiating factors is their amazing designs. They built the biggest technology company based on great designs - outperforming companies that solely focused on functionality.
Here is a redesign of Todoist's and Wedoist's fronpages. It's not live yet, but will be live during the next week:
Today's entrepreneurs are spoiled brats
This is a comment I left on Hacker News covering Herval's complaints about Start-Up Chile:
Pythonic JavaScript to CoffeeScript compiler
I am rewriting some of my old JavaScript code into CoffeeScript. I feel more happy in CoffeeScript and I think I produce more readable and more maintainable code. It also offers some great features such as classes. To help me on this transition I use js2coffee which compiles JavaScript to CoffeeScript. I have made an extension of js2coffee that is a bit more Pythonic (produces Python looking CoffeeScript code :-)).
I have before covered CoffeeScript and you should read my post if you don't know what CoffeeScript is: The Pythonic extension of js2coffeejs2coffee is great, but I don't like some of the code it produces as it's more similar to Ruby than Python. I have forked js2coffee and provided a --pythonic option which does following things:
Here's a little showcase of what --pythonic does different: $ ~/> cat test.'sThe JavaScript code we are compiling to CoffeeScript: function helloWorld(some_value) {
if(some_value != 'hello')
return;
aFunctionCall('hello');
return "value";
}
$ js2coffee test.jsCompiling it via the standard js2coffee produces following code: helloWorld = (some_value) ->
return unless some_value is "hello"
aFunctionCall "hello"
"value"
$ js2coffee --pythonic test.jsCompiling it with the --pythonic option produced following code - - which for me is easier to understand given my Python background: helloWorld = (some_value) ->
return if some_value isnt "hello"
aFunctionCall("hello")
return "value"
Google Closure + CoffeeScript?Another project I am currently working on is to improve CoffeeScript's compiler so it can work with Google Closure. What this enables is to write high-level code in CoffeeScript that gets minimized and optimized by Google Closure's compiler (which does non-trivial code transformations and optimizations). There's already a project that does something similar to this: Books that I read (new section)
As a new section I will link (and maybe review) books that I read or want to read.
Currently reading: Autobiography of a Yogi by Paramahansa Yogananda
Following Ask HN: Best book you read in 2011 I have added following to my reading list: The Undiscovered Self by Carl G. Jung
The Checklist Manifesto: How to Get Things Right by Atul Gawande
Filtering through vimgrep results using regular expressions
Here's a neat trick you can do with vimgrep / ack.vim to filter through results using regular expressions. This is super useful when doing a refactoring. Let me show an example of how I used this trick recently.
I wanted to rename todoist.users to todoist.apps.sessions, since it was a more appropriate name. Here's how I made sure that all the imports got updated: 1. Search for all occurrences of users in Python files, :vimgrep "users" **/*.py":
2. Show all results in a quick list window by typing :cope in command mode:
3. Copy the results of the quick list window into a new buffer. By for example typing gg => V => G => y => :ene => p 4. Delete all lines that have models in them (as we want to ignore todoist.models.users). You do this by typing :g/models/d. 5. Delete all lines that don't have import in them (we want only to focus on import lines). You do this by typing :g!/import/d After this step I only see results that are relevant for my refactoring. A bonus for you vimrcAdd this to your .vimrc to quickly transform your quicklist into a new buffer that's syntax highlighted: map <leader>cc :botright cope<cr> map <leader>co ggVGy:tabnew<cr>:set syntax=qf<cr>pgg Anytime you press leader + co in a quicklist window it will isolate the quicklist in a new tab and syntax highlight it. py_static_check: Statically check your Python code for errors
I have released py_static_check, an useful tool that can statically check your Python code for common errors. Statically implies that the code isn't evaluated.
py_static_check is based on pyflakes and adds following features:
To install it do following: $ sudo easy_install py_static_check To fork it (and improve it) go to github: Here are some of the things py_static_check can do. Catch undefined names, even for star importsExample code: from os import *
def function_with_error():
print path
print paths
star_imports.py: import os
STAR_IMPORTS = {
'os': os.__all__,
}
Running it with py_static_check (with -s argument): $ py_static_check -s tests/star_import.py tests/undefined_name_star.py tests/undefined_name.py:5: undefined name 'paths Running it with pyflakes: $ pyflakes tests/undefined_name_star.py tests/undefined_name_star.py:6: 'from os import *' used; unable to detect undefined names Ignore not used warningsExample code: from os import path
Running it with py_static_check (with -i argument): $ py_static_check -i tests/ignore_not_used.py $ py_static_check tests/ignore_not_used.py tests/ignore_not_used.py:10: 'path' imported but unused Assigned but never usedLike pyflakes it can catch a lot of errors, such as defining a variable without using it. Example code: def some_function():
def inner_fn():
local_var = ""
Running it with py_static_check: $ py_static_check tests/assigned_but_never_used.py tests/assigned_but_never_used.py:8: local variable 'local_var' is assigned to but never used
Making ugly code more beautiful using Python's with statement
I have before featured Python 2.5's with statement and I am using it more and more. It makes code more readable and beatiful. Yesterday I used it in a beautiful way and I want to highlight it here.
My task was to ensure that the correct language is set when sending out emails. My first iteration used following code: #Remember the current language
current_lang = get_current_lang()
#Set language by uid if possible
user_lang = get_lang_by_uid(uid)
if user_lang:
set_current_lang(user_lang)
#… MAIL CODE …
#Revert back to old language
set_current_lang(current_lang)
Now this is fine and well, but not that clean, especially when you have to use it in multiple places. I knew there must be a better way to do this. After some iterations I came up with this marvel: with set_lang_by_uid(uid):
#… MAIL CODE …
A one liner that I can use everywhere! The implementation of set_lang_by_uidImplementing set_lang_by_uid is trivial - that's the great thing about using the with statement! Here's how it's implemented: from contextlib import contextmanager
@contextmanager
def set_lang_by_uid(uid):
user_lang = get_lang_by_uid(uid)
if user_lang:
current_lang = get_current_lang()
set_current_lang(user_lang)
yield
set_current_lang(current_lang)
else:
yield
More referencesCheck out my older writing on the with statement: Target the Global Market
Translate to other languages, that's one of the most important lessons I learned from Plurk. We initially set to build a product that targeted North American market, but we grew mostly in Asia-Pacific. This could have never happened if we didn't have Plurk translated to over 30 languages.
I am trying to replicate the same strategy for Todoist and Wedoist. Recently we released Wedoist in Spanish and we have contracted 9 other translators to bring Wedoist in top 10 languages of the world. Same will happen to Todoist. Translating your product to other languages is also very cheap - - we pay about $100 for 4000 words on average. You can find great translators on Elance.
Truncating text using only CSS
Here's how you can truncate text using only CSS. It works in IE 6+, Safari 4+, Firefox 7+, Opera 11+ and Chrome 10+:
.link_truncated {
text-overflow: ellipsis;
display: inline-block;
width: 275px;
white-space: nowrap;
overflow: hidden;
vertical-align: top;
}
How it looks like when rendered:
|
|