Archive for May, 2008


May 30, 2008

I’ve noticed that this has been the typical layout of my screen in the last three weeks:

So I have access to:

  • source files and SVN controls on them
  • typical editor with ability to jump to declarations and a margin at col 80
  • a class/module browser
  • Can navigate files with ALT+TAB
  • at the bottom: SVN commit logs, test results, Python run results
  • top right: quick switch between SVN remote file access, PyDev, and debugger (which actually sucks since I can’t look up my variables)
  • Also cygwin for running scripts, launching a DrP standalone portal, and basic Unix commands
  • IPython for debugging purposes; function source and docstrings
  • browser with tabs for: Elixir documentation, SQL Alchemy documentation, utoronto email, my blog, and one extra for quick navigation to something not mentioned above.

Just something I’ve noticed.

Saturday I think I’ll have to start thinking about the user interface pretty soon:

  • creating a new project
  • editing a project
  • event log
  • view tickets (includes querying)
  • ticket details

Based on my own experiences, I think that users that want to create several quick-and-dirty tickets should be able to do that seamlessly. Others who want more detail will always be able to fill in more fields and take their time. This brings up another issue: we’ve been working under the assumption that fields will have default values which will take effect if no values are provided in a ticket. What if the default value doesn’t always apply? We don’t want to have to handle NULLs in the database and optional and non-optional fields for the user. But if a user wants to quickly fill in the description, all the other fields in her ticket will be their default and this information may be misleading to others.

I think speed is the key (no, not computation complexity, user speed). I should be able to specify all the fields I want when I create a new project, I should be able to change and delete them in place without having to click more than once, I should be able to view parts of a ticket that I feel are important right away (or with a mouse-over). I should also be able to view the field values of a ticket in a nice tabular, succinct form. Changes to a particular field should also be simple and not require more than one click.


May 30

May 30, 2008

On Thursday, Greg presented me with two options with respect to proceeding with the changes to the ticketing system

  1. implement the new system and integrate it in-place (fix whatever breaks as a result of these changes), then proceed to the UI design
  2. create a small standalone system and write a UI for that to test whether the concepts in the code will really help in practice

I had chosen option no. 1. Option 2 requires building a lot of scaffolding that looks more and more expensive the more I learn about DrP. In fact, I wouldn’t be surprised if I spent my entire employment building the scaffolding. Moreover, I it is my opinion that the design of the system is abstract enough, inasmuch as changes are easy, that issues discovered in the UI will not warrant huge changes to the underlying code or design.

That’s quite a gamble, actually, but I feel that the scaffolding required to implement option 2 is far too expensive in the time I have.

Greg also made me privy of the fact that SQLite *will* be used in some portals of DrP in production so we need to support it as well. Hence we need to manually maintain foreign key constraints (in particular, ondelete). In other databases, this can be achieved without the addition of a single line of code (by using constraints as I had planned originally), but because of SQLite, we have to use explicit functions.

There are two options (or more):

  • write mapper extension for Project, User, Membership, Milestone, Ticket, etc that are triggered before/after inserts/updates/deletes
  • issue signals in the API functions for adding/deleting and have listener functions take action as necessary

Both of these options are actually quite similar, more than one may think. But mapper extensions are a bit clunky and the existing code already uses signals (actually it uses a combination of mappers and signals). I am going to go with option 2.

2:00 I spent most of the day starting from 11:00 managing these constraints, writing tests and making sure they pass. By the end of the day, I was happy with the results, more or less.

3:00 I really want to move on to queries in, but things just keep popping up; I need to fix so many things in and I wish a time would come when I can say that I’m done with this part. I feel like I’m in a position between working on the API and moving on to queries; I don’t like it here because I can’t say that I’ve done A and I’m moving on to B… no, I’m done X% of A and I’m working on the first Y% of B. It’s not frustrating, just odd. This doesn’t really happen in class.

Here’s where I notice that I have a lot of tasks to be done but I’m not filing tickets. There are several reasons:

  • Even though I can query tickets assigned to me, I feel uncomfortable having my tickets in a project where there are thousands of other tickets; I want mine to be apart somehow. I have the newt milestone, perhaps I should start using it. Why haven’t I?
  • It would take me longer to file a ticket than just write a very short comment in the code itself and refer to it.
  • I need very little information in my tickets so I don’t want to have to fill out too much information
  • I can’t stand it when my tools aren’t consolidated; I’d rather just use whatever Eclipse offers (TODO task tag)

end of the day Jeff doesn’t like whitespace… I do. When I’m heading out, he brings my attention to a function I wrote to retrieve all tickets pointing to a given milestone where the tickets have some field X with some value Y. My query is:

def __tickets_with_field_criteria(self, field, value):
    return [f.ticket for f in MilestoneFields.query.filter_by(value=self)
                if f.ticket.fields[field] == value]

I’m doing two queries here: “MilestoneFields.query.filter_by(value=self)” and “f.ticket.fields[field]”. So this will take very long on a large set of tickets. I’m not very good at writing a query then looking back and asking myself what it’s actually doing and how feasable that is. 

May 29

May 29, 2008

morning I spent my morning documenting the code and finishing writing the tests for and There were some problems, but they were nothing out of the ordinary for programmers. I made sure that a lot of the pi functions’ parameters were what they were in the old system so changes to the rest of DrP would be minimal. I also had problems with some tests failing, and that’s still a small issue that, I hope, will be resolved soon enough so I can move on to working with

One of the sets of decisions I had to make was in regards to milestones and how they now work. Nothing drastic was done, but tickets are no longer required to be part of a milestone. This may make it seem like there are going to be stray tickets that won’t automatically be deleted when their corresponding milestone is removed, but that’s not really the case. Tickets are never actually removed from the database so whether they’re bound to a milestone or not really makes no difference (even in they old system they would just be redirected to the someday milestone upon deletion of a milestone). Users can still associate tickets with a milestone, but there will be no operation associated with that relation (e.g. cascade on delete, or redirect on delete).

This brings up an interesting point; what if users want a certain attribute/field of a ticket to hold a certain action/operation. What if a user-defined field ‘priority’ is meant to hide all tickets when querying if the value is ‘low’? Currently, the answer is that users will have to indicate that in each query and cannot assign triggers to fields. That sounds like something not even worth considering in a first version.

afternoon From about 11:30 to about… oh, 3:00 (minus the status-update meeting the DrP guys had with Greg), I was brought to my knees by foreign key constraints. I had set up the relations in the database so that tickets and field-descriptions would be deleted when projects are deleted, fields would be deleted if field-descriptions or tickets are deleted, and the like. Now, a lot of things (like tickets and field-descriptions) can’t actually be deleted, but we should have something in place in case they ever are. I used the ondelete=’cascade’ attribute for the ManyToOne relation Elixir offers.

But the tests (which were thorough yet concise,  and well thought-out) failed. I spent hours thinking through the logic, running tests, using the debugger (which, in PyDev, apparently doesn’t let you query the value of variables not already displayed), getting help from IPython, and reading the SQLAlchemy/ELixir documentation (and even the source code). Even Jeff was baffled. He looked at the SQL queries that Alchemy was generating and monitoring the database tables; there were triggers on the deletion of records. What the hell was going on?

Finally, Jeff started to read some documentation for SQLite in Alchemy (after a hunch he had just had), and eventually arrived at a relevant line of text regarding the ondelete attribute. He started to read to me (and himself) something to the effect of “Note, this is not supported in SQLite.” Oh my God!

So what do you do when you need to test something but your environment or scaffolding doesn’t support some necessary feature? Well, most of the work in this case is handled by library functions; if I screwed up in coding the schema, it would have been in the not including the ondelete attribute and/or including it in the wrong place(s). I will leave the tests for now and come back to them when I (or someone else) have the necessary environment set up. I feel this can be left for now and I can move on without too much worry.

Alternatively, I offered to write mapper extensions (classes with functions that called on database updates, inserts, etc). We could manually delete fields on deletion of projects, for example. Although I offered to do this, I knew this was terrible and I was hoping Jeff would agree with me… and he did. We agree that SQLite will never be used in production and we can assume the production database will support triggers.


May 28, 2008

I’ve been working to finish the database stuff, the API functions, and ensure the current tests pass. That’s all done and my next step is to write more tests. The entire afternoon, after doing the above, I’ve been working on the documentation available here. I’ve made it very thorough and updated it to match the new stuff. It’s pretty much complete except for the fact that I need to update the database schema picture tonight. The documentation is thorough, but I’m not sure that it’s organized. Oh well, not important right now.

8:20 ==>

I forgot to blog that a few days ago I had an idea: I wanted to create a first ticket for each project, one that users wouldn’t be able to touch. It would contain the default values for each field-description, their type, etc. (It would have to be updated when new field-descriptions are inserted). This ticket could then be cloned when inserting a new ticket into the database instead of having to go through each field-description looking up its default value, parsing it, and what not. It would also reduce the need to have an explicit class for supported data type (since we wouldn’t need the parse function); the classes could be generated automatically in a loop. Jeff, however, vetoed the idea since special case tickets would be bad.

May 28

May 28, 2008

9:00 Yesterday Jeff moved the created field of tickets to the ticket class from the environment setup. I didn’t like having some field in the ticket class and other (also mandatory) fields in the environment setup. So I moved the mandatory fields from the setup to the ticket class. Later on, after some debate, Jeff convinced me that the created field will never come in contact with the user and it’s special enough to warrant splitting it from the other mandatory fields. I suggested keeping reporter with the created field as well… at first Jeff disagreed, and then he agreed when I was ready to move it.

I also changed the indentation of the Elixir stuff in the ticket class as well as the other classes. It looked much more clean and readable to me, but Jeff pointed me to the Python style guide which discouraged my indentation practice. It escapes me why that’s bad practice; it’s much cleaner to read. The only thing I can think of is that someone searching (e.g. grep) the file will have to take spaces and newlines into account when providing a search pattern (because “a = b” is not the same as “a      = b”).

I also had it so that the fetch functions for tickets and field_descriptions returned a ticket/field-description when given enough data, and a list of them if given vague data. Jeff discouraged this as well and so I changed it. I understood this decision.

I was struggling quite a bit with the order_by clause for OneToMany relations. I was trying to order the TicketChange reference in tickets by their time, descending. I added a minus in front of the name as indicated in the documentation, but tests failed with and without the minus. SO I just decided to order it by time (however) and return the last element (as opposed to the first) when checking the last_modified time. I would much rather it sort correctly and I would index the list at 0, than sort the list in reverse order and index the list at -1. By the way, Jeff reminded me of negative indexes; I was using len(X)-1. I would have caught on eventually, though.

10:00 I was having many problems with fields and field_descriptions. There is a reference to field_descriptions in the field table, but that reference is called field. This was causing me to pass in one when the other was required. It was really confusing and hard to refactor (because refactoring a, b = b, a is not really good; once you set a = b, then how’s Eclipse going to know which b’s to replace with a?)

Many tests were also failing because I wasn’t ordering the field reference in tickets correctly. I was ordering it by name, but fields don’t have a name attribute; field_descriptions do. So I changed the order_by=’name’ to order_by=’field_description_name’ after renaming the reference to field_descriptions in field to field_description (to avoid confusion).

I also added a loop to collect all the classes in that are data-type tables in the database and out them into a dictionary. THe dictionary’s keys are the names of the classes and the values are the classes (not instances). This was the code that achieved that:

TYPES = dict((c.__name__, c) for c in filter(isclass, locals().values())
                          if issubclass(c, Field))

THis allowed me to finish the API methods in I had to account for the change in my understanding of fields and field_descriptions. After that I completed the add_ticket functions thanks to the new TYPES variable (which actually already existed in a previous revision). Many tests were still failing.

11:00 The API was pretty much done, but tests were failing. The error messages were descriptive, but they didn’t help localize the problem to code that I had written (they were very specific about library functions). After thoroughly reading my test code and really understanding what I wanted to test, I realized that I wasn’t flushing data to the database when I should’ve been. Since creating new tickets and new field_descriptions affect many records in the database (not just the new record), I needed to flush. So I added flush() to the end of the add_ticket and add_field functions. add_field, by the way, is actually supposed to be add_field_description.

I was having trouble with one of the tests. I was checking whether an expected list was equal to an actual list. After very careful thinking, I asked Jeff. He used the debugger but to no avail (I knew it wouldn’t help in this case). So he just used print statements (I probably should’ve tried that too, but I’ve dropped that habit for a while now). We found that the two lists were the same, but they their items weren’t in the same order. First I did L.srted == L.sorted, but Jeff suggested sort(L) == sort(M). It worked, but Jeff then suggested set(L) == set(M) which makes what we’re trying to do more explicit. I agree, but what if duplicates matter?

1:20 I spent a few minutes in the morning trying to convince Jeff not to change the database schema anymore barring some trouble in creating the API functions. He disagreed. I was adement because I thought that we need to have a deliverable at some point that we could enhance only later. I wanted to move on. I knew that the task would never really be done and that the schema could always be improved, but I no longer wanted to be bogged down with that. I wanted to move on and come back later if necessary.

The API is basically done now, and I assume the schema is ready. What I’m going to do now is beef up the tests and modify the API as needed. I wish I could clean up the code, but legacy code doesn’t always permit that. THe step after that will be to go through DrP and make sure it knows about the new system and invoked functions correctly. A major part of this will be modifying

But before I do that, I want to update the wiki page explaining the system. Doing this gives me a very good understanding of the design and clarifies flaws that may exist. Documenting the system has been an invaluable asset to my understanding. When I go home, I also want to update the DB schema jpeg to update it and fix an error.


May 27, 2008

10:45 This is a list of the files that will have to be audited after the ticketing system is “complete”:

  • drproject/admin/

  • drproject/admin/

  • drproject/attachment/

  • drproject/config/

  • drproject/dashboard/dashboard/harvesters/

  • drproject/dashboard/dashboard/harvesters/

  • drproject/dashboard/dashboard/harvesters/retrievers/

  • drproject/dashboard/dashboard/

  • drproject/userpages/

  • drproject/userpages/

  • drproject/userpages/tests/

  • drproject/

  • drproject/

May 27

May 27, 2008

9:00 Here’s a link to a detailed description of the new ticketing system and how it works. It includes the finalized and detailed database schema. I was working on it from about 8:00 last night to about midnight.

Yesterday afternoon Jeff was teaching me a lot about testing and I learned a lot from him.

  • tests should be breadth first
  • they should only tests code I wrote; not verify the correctness of library functions I’m using
    • e.g. testing whether adding a new field-description to the database which actually already exists in the database is superfluous; all that’s testing is that Elixir correctly maintains integrity constraints
  • I should test something only once and never repeat that test in any other test_ method; this requires more careful thinking than I originally thought. I find I have to think a little bit and only then do I see that, wait a minute, what am I doing here… I’ve tested this functionality already.
  • tests are really heavy so minimizing the operations is a good idea
  • I should write tests assuming that unwritten api functions already exist; that’ll give me a good idea of what functions need to be written and what parameters are reasonable
  • hard coding data in tests, contrary to what I thought, is not a good idea
    • hard coding data is bad of course, but I’ve always done that in test cases because I felt otherwise that the test may be “cheating”
  • tests should be extremely explicit. I think I’m learning slowly to look at my tests and as k myself what am I actually testing. Then I can go and make that as explicit as possible
  • tests should be written such that, when they fail, you know exactly where the problem lies and not have to modify the test in order to localize the problem further

Yesterday, I also learned, on an unrelated note, that there are two sentences I am really afraid of. When I hear them, my heart drops and my brain scrambles to look for answers/solutions/whatever. I heard the first sentence in my very first job; it was “It has come to our attention that you …”. The second I heard yesterday: “I don’t know if you’ve been listening or not, but …” Those sentences give me a heart attack.

9:40 So the database schema has been more or less finalized and we have some initial tests (which look very nice thanks to Jeff). Today I’m going to work on the api functions. I’m also thinking of writing some __X__ functions (those built in ones); i.e. operator overloading so that we can do things like “del f” which could take care of deleting a field-description f (by calling the api functions).

afternoon Jeff has taught me to make incremental changes to the ticketing system. I.e. we should have the system very much like what it was before and make changes slowly, one at a time. This means setting up the API functions very much like they were before (only this time, taking care to factor in the fields concept). This is good advice despite the fact that I would very much like to do a complete overhaul based on new ideas and better software engineering practices. But sadly, that would mean that I would have to go all over DrProject modifying function calls and making sure that return values are handled correctly and what not. Incremental is good.

One of the things that was integral to the ticketing system before was a reference to TicketChanges in the Ticket class. So I’ve added a OneToMany relationship from tickets to TicketChanges. I thought that the database schema was done; apparently I was wrong. It seems that whenever I think I’m done with one phase of this project, it keeps creeping back. I wonder when I can say for sure that the database schema is now final.

Jeff told me to move the “created” field out of the environment setup and make it a field in the ticket table. This is how I had it before Jeff told me to move it to the environment setup; I guess he changed his mind. But now there are some fields in the ticket table, and some fields setup during the the environment creation; I hate this. This is bad and it doesn’t make much sense to me. If Jeff wants “created” in the ticket table then I’ll move every mandatory field to the ticket table as well and have only default (non-mandatory) fields setup in the environment creation.

I moved a bunch of the API methods to the file instead of having them in the file. This is how it was before. This is one of the things about the old design I don’t like; although it makes sense for some functions to be here in, it screws up the MVC pattern. I would rather everything be in includes a fetch function to fetch tickets. I’ve made it a classmethod so that instances of a Ticket won’t have to be created in order to retrieve tickets. I’ve also made it so that the function returns a specific ticket if given the project and ticket id, and all tickets in a given project if given only the project. So it’ll return either a ticket, or a list of tickets.

I’ve also created a function to find out the last modify time of a ticket. Jeff told me to turn what I orginally had as a variable in to a function. That makes sense since the last modify time is really the time of the most recent TicketChange. When coding this function, I made the mistake of not considering the case where there are no previous TicketChanges. Jeff corrected me and I made it so that, in such a case, the ticket creation time is returned.

I’ve also created setvalue and changecomment functions to match what was there in the old API. There is also a function get_changelog which returns all TicketChanges associated with a given ticket. I originally had this function return a list of TicketChange objects, but Jeff reminded me of legacy API requirements and so I made it return some sort of dictionary with keys mapping to tuples or something or other. What a mess it was… you should just return objects and the caller can do whatever with them.

I also added a fetch method for field_descriptions. It also returns either a field description object, or a list of them.

A minor change I made to field objects was to make their parse_unicode method static (i.e. I gave it a classmethod decorator) so that instances would not be needed to parse default values from Unicode.

In I have three new functions (and I’ve deleted many of the old ones). A function for creating new tickets, one for creating new field descriptions, and one for deleting field descriptions. There are a lot of integrity constraints that need to be taken care of and that’s why these functions are in place. For example, if a new field description is added, all old tickets must be linked to that new field with the default value.

I have yet to complete the function for creating tickets. That’s because I realized that it requires me to know the type of existing fields in the project. But I removed the type field from that table; that was one of the purposes of Jeff’s new database schema. I guess we need the type after all.

I find that I have a very hard time anticipating how functions need to look. I can try and assume reasonable parameters to a function, for example, but it invariably turns out that I need another parameter without which things won’t work. Thus I make design decisions and have API method calls (in tests for example), but when I go to write those functions, I find I can’t implement them the way I thought. Either I’m missing parameters, or I assumed the wrong type of parameter (e.g. project name instead of project instance), or whatever. It’s really annoying.


May 26, 2008

9:00 Okay so here’s the final database schema we’re going with:

The circles mean that the table with the circle is the one that holds the reference (foreign key).
The brace indicates inheritance (i.e. a join will be done).
The orange text is the default value for the associated field.
A line with a quantification at the end of it means the table the line’s coming from has the specified quantity of references to the table the line is going to

Not as symetrical as I would’ve hoped.

May 26

May 26, 2008

8:40 On Sunday, Jeff sent me an email outlining a new database schema idea he had. Actually, it wasn’t really much different from what we already had, but it did have one or two advantages. I’m not as experienced as Jeff so I didn’t really see any real benefits to it.

Today I’m going to work on:

  1. auditing the new schema and making sure things hold together
  2. deciding what fields should be included in the schema and what should be populated in the environment setup
  3. writing tests to see what I may want to be able to do with projects, tickets, fields, etc and what that entails. This will help me write helper functions and whatever else is needed to scafold the new system.

Another lesson I’ve learned over the weekend:

  • when writing open source code, “extensible” means “other programmers know exactly what to modify in order to achieve their goals”; it doesn’t mean “other programmers are provided with an API and base classes that they can extend to extend the system”

In other words, open source code should be easy to modify, not extend. True, or not? I wonder.

9:40 What I may want to query:

  • projects
    • retrieve associated ticket(s)
    • add / remove ticket
    • retrieve associated field
    • add / remove field
  • ticket
    • retrieve all field values
    • modify the value of a field
  • field
    • change the default value
    • change the type (remove then add another field ?)

1:40 The database schema has been finalized (hopefully). I’ve written some initial tests for creating tickets, creating fields, creating tickets and tesing their fields, and creating fields and testing existing tickets. I’ve also created a test to validate the fields that are set up as part of the environment setup; but they fail. I’m looking into that.

I don’t think we need any mapper extensions, and I think all the references and ondeletes/onupdates are correct and complete. I’ve analyzed some of the circular references i nthe database and their okay; Jeff agrees.

2:10 For below, a FieldDescription is all the metadata associated with a ticket field for a given project. A Field, on the other hand, is the actual data associated with a particular ticket. A lot of the queries below will never be used, but I’ve included them for completeness and to get a better understanding of the schema. I’ve also indicated how important a query might be: high, medium, low, never.


  • Project: Project.query.filter_by(name=<N>)
    • medium Retrieving a project’s FieldDescriptions
      • FieldDescription.query.filter_by(project=<P>)
    • never Retrieving a projects (concrete) Fields
      • Field.query.filter_by(field.project=<P>)
    • never Retrieving a project’s Tickets
      • Ticket.query.filter_by(project=<P>)
  • Tickets: Ticket.query.filter_by(project=<P>, id=<I>)
    • medium Retrieving a ticket’s associated Project
      •  <T>.project
    • high Retrieving a ticket’s Fields LIST
      • <T>.fields
    • never Retrieving a ticket’s associated FieldDescriptions
      • FieldDescription.query.filter_by(project=<T.project>)
  • FieldDescriptions: Ticket.query.filter_by(project=<P>, name=<N>)
    • medium Retrieving a FieldDescription’s associated Project
      • <FD>.project
    • never Retrieving a FieldDescription’s associated Tickets
      • Ticket.query.filter_by(project=<FD>.project)
    • never Retrieving a FieldDescription’s associated Fields
      • Field.query.filter_by(field=<FD>)
  • Fields: indexed by ID (not really practical to retrieve it by itself)
    • low Retrieving a Field’s associated Project
      • <F>.field.project
      • <F>.ticket.project
    • high Retrieving a Field’s associated FieldDescription
      • <F>.field
    • low Retrieving a Field’s associated Ticket
      • <F>.ticket


  • high Changing a Ticket / Field
    • TicketChange(project=<P>, ticket=<T>, time=cur_time, author=<A>,  field=<F>,        
                                 oldvalue=<OV>, newvalue=<NV>)
  • low Changing a FieldDescription
    • <F>.<X> = <Y>

Adding / Removing

  • high Tickets
    • <t> = Ticket(project=<P>, …)
    • TODO: not sure how to remove a ticket yet
  • medium FieldDescriptions
    • <f> = FieldDescription(project=<P>, name=<N>, default=<D>)
    • <F>.active = False
  • never Fields
    • Add/remove by adding/removing the associated ticket

Summary of Lessons

May 23, 2008

  • reading documentation only goes so far; get your hands dirty in the code
  • don’t be afraid to throw away code, but don’t do administrative tasks that you know are irrelevant
  • your smart enough to understand code after reading it, but have someone explain the design patterns in the system as a whole
  • read relevant chunks of code to identify functions you can use later on to your advantage
  • draw pictures and document your progress
  • programming languages have their own idiosyncrasies, use them; don’t try to simulate language syntax A in language B
  • you’re expected to get little done in the first week besides understanding the tools and the code
  • use big-boy tools. I can’t respect software engineers who don’t use big-boy tools
  • when working on a particular part of a system, be sure to start by understanding how the entire system works as a whole
  • use scripts and aliases. Ask about existing ones
  • zombies can code… but their code is retarded
  • find your best working time, environment, and whatever else to make you more productive; apply what you learn, but know that theory defines black and white whereas, in practice, there’s a lot of gray
  • Remember that you can always revert to a previous revision, so calm down
  • elegant code is preferred over efficient code, sensible code is preferred over elegant code
  • work on the same platform and with the same tools as you teammates
  • don’t be afraid to ask for help, but consolidate your questions
  • when you add something static (e.g. data types supported by the system), pretend your the next programmer coming along… what will you have to do if you wanted to support another data type? What will you have to modify? Where will you have to look?
  • it’s easy to find that there are bugs in the code, it’s harder to realize there are bugs in the design
  • organize your thoughts before you code. That’s not the same as sturdy programming
  • find it somewhere before you build your own
  • just because it works doesn’t mean it’s right
  • how do we live without continuous integration?
  • a system with many plugin components is accident prone
  • require little from the user
  • the first week will be spent trying new things, experimenting, researching, redoing things
  • if you think a task will take one day, it’ll take five
  • do you really need to do that manually? Can’t you automate it? Dynamically populate it? Use introspection? Use decorators?
  • do you know the specs for the new _? Really?
  • did you consider security? Extensibility? Maintenance? Comprehension?
  • start small, add more later
  • use breadth first testing, run your tests often
  • look at what you can do with the most abstract layer of the library, forget how that’ll be translated into the deeper layers
  • list the possibilities