Few months ago, someone from the team was showing off GitHub Co-pilot to me. “If you just write the function name, it will write the entire function”, the engineer was showing gushingly to me. He wrote a function name and the tool wrote a reasonable function with the business logic. It was impressive, but I could not brush off the feeling that this is just sleight of hand. As someone who has written code for almost 30 years, my instinct told me that there is something deeper than just smart pattern matching to writing good code.
GitHub Co-pilot
I started writing code in the 90s with GW Basic as my first language. You had to write line-numbers to define the sequence of execution. The term “developer tooling” was non-existent. Over the years, I moved on to Visual Basic as the platform of choice. Microsoft Visual Studio in the late 90s and early 2000s was my best ever experience of developer tooling. It had the ability to visually design screens, and add code to handle events. Visual Studio had amazing code completion capabilities as well. The editor could inspect the object you are typing and give you options as you typed so you did not have to remember exact variable or method names and never make a typo. Eclipse and Netbeans for Java were poorly executed copies of Visual Studio.
When I moved to Python in 2005 to write the project which is now Frappe Framework, it was back to square one. I mostly used simple text editors - TextMate on Mac was my editor of choice to write code. There was no visual page designer or code completion capability. This meant that the code had to be written in a way that was extremely intuitive and consistent. Anytime you need to call the function, you should be able to recall it from memory without the need for any referencing. This also means that there must be fewer but more powerful and general purpose functions rather than a large number of functions, each doing a very specific task.
Another design pattern that became important is “object orientation”, which means hiding complex functionality inside objects so that you can call it contextually. Since the framework is a very complex piece of software, using classes was a great way to partition the complexity and hide it from the higher level interfaces.
All of this made the framework very “composable”. You did not need to remember a large number of library functions to get to working code. Another trick I used was to import all the major functions inside the namespace frappe
. Using the frappe module, it was possible to reach almost every corner of the vast and complex framework.
For example, if you wanted to create a new object of type Task, you had to just use the frappe.get_doc
method and then insert the document that was created.
task = frappe.get_doc({
doctype: “Task”,
title: “Do something”,
due_date: “2023-08-08”
})
task.insert()
This is the cleanest possible interface to create a document of type Task. As a developer, you don’t need to write code to connect to a database, or convert the date string to a date object or add creation timestamps. The framework was designed to handle all repetitive tasks inside the Document class.
You don’t even need to remember where the module Task is located in your Python module structure, so you can create objects of any type. Even objects internal to the framework were mapped as classes (DocTypes). If you wanted to create a new Role
in the system, you would still use the frappe.get_doc({doctype: "Role", role_name: "Designer"}).insert()
. Much like the UNIX mantra that everything is a file, in Frappe, everything is a DocType. This unified interface makes every part of the system instantly accessible and creates a common paradigm for invoking functionality.
Other than the document model, using lower level stuff was also a few commands away. If you wanted to write a direct SQL query, you would use frappe.db.sql
and get a result set. If you want to throw an exception, just use frappe.throw(“Something went wrong”)
. This would manage logging and also notifying the user of the exception.
When you use frappe, you realise that the real power of frappe is how fast you can build complex applications using just 5-10 basic commands. You can check out one of the most complex controllers (sales_invoice.py) to see the simplicity of the developer interface.
This was not really a design requirement, but a happy coincidence because of the constraints under which the framework was built. Not having access to modern developer tooling, forget using AI based assistance, made sure that the programing interface was as simple as possible.
Other than framework, even on the application side, using very simple and intuitive names for everything became very important. In the initial years, we would actively rename files, classes, variables and methods so that it was “intuitive” to the next engineer who was working on it and could work on it without referring to too much documentation or code. Having a very semantically consistent vocabulary helped us add an immense amount of complexity to both the framework and ERPNext.
Writing composable software means that the engineers building on top of your code, should focus on their problems, not worry about finding the right function call. This probably boils down to:
- Write fewer but more powerful, deeper functions.
- Create a unified interface to access everything in your system. (Similar to "everything is a file")
- Use semantically consistent names. Code should be as close to the English language as possible. Every name must be consistent with what it means.
- Write layered code. Use encapsulation to expose functionality only when it's needed, and avoid unnecessary module imports.
- Avoid boilerplate (repetitive) code. Don’t make people write unnecessary boilerplate code, that just adds to the cognitive load of the engineer.
For me, not using developer tooling helped me to make these choices which had a multiple order impact on the code base. Today there are probably a few thousand engineers building and customising applications using frappe framework. Sometimes I see modern web frameworks and cringe at the amount of boilerplate code engineers have to write to even get basic functionality up. Sometimes I wonder if developer tooling is making the entire profession boring and repetitive. Writing composable software is extremely delightful, even to the person building the basic building blocks, and it would be amazing if all software gets written this way.