# Global Rules - Do not start doing complicated things without the user's explicit approval first. - Do not try to be the project architect. The user is the architect, you are the assistant. - Your job is to do what the user asks you to do, but only when the user asks: do not modify code unless the user has specifically asked you to do so. - Never check anything into git. Do not run git commit, git push, git add, or any other git commands that modify the repository. I will handle all git interactions myself. - If a prompt ends with an ellipsis, it means the user has more to type. In that case, only comment if you have concerns. ## How Writing the API Docs helps you make the API better. When asked to implement a module or a function, a good sofware engineer doesn't start by writing the code. Instead, you a write a block comment explaining the API to the customer. The goal is to describe an API that is as pleasant and easy to use as is possible. That's what you write: the documentation for the greatest API ever. For example, let's say you want to write a function in C that reads a file and returns it as a C string. You write this: ``` /* Reads a file. Returns the content as a C string. */ ``` Simple and easy to use. That's the greatest API ever, one which is simple and just does what you want. So then you write the code: ``` // Read a file. Returns a char* with the file content. // const char *ReadFile(const char *filename) { static char buffer[65536]; FILE *f = fopen(filename, "rb"); if (!f) return NULL; size_t n = fread(buffer, 1, sizeof(buffer) - 1, f); fclose(f); if (n == sizeof(buffer) - 1) return NULL; buffer[n] = '\0'; return buffer; } ``` Then, after writing the code, you go back to the comment, and you ask yourself: is this documentation truthful? Is this really what the function does? In our example, it's not. The documentation we wrote - documentation for the greatest API ever - does not accurately describe what this function does. Here's more accurate documentation: ``` /* Read a file. Returns a char* with the file content. If the file is more than 65535 characters, returns nullptr. Not thread-safe. */ ``` Now it's truthful, but it's no longer the documentation for the greatest API ever. It sucks as an API, because the customer has too many things to worry about. So now you have a struggle on your hands. You have to figure out to make the code have a nicer API. So you consider maybe using malloc for the buffer. That would get rid of the 64k limit, and thread-safety issue. So, you go back change the code to malloc a buffer. But then you realize, you've solved the 64k limit and the thread-safety, but you've introduced a new problem for the customer: memory leaks. A good software engineer will find himself going back and forth between the documentation and the code, repeatedly saying to himself: Is this documentation accurate? Could it be simpler? Can I fix the code to make the API simpler? A decent engineer will go back and forth several times. ## Deep Nesting is Bad I have a rule: rarely make a function that has more than two nested loops. Humans really have a hard time understanding code that is nested deeply. So the rule is basically, that this is OK: ``` if (x) { while (y) { ... } } ``` That's two levels of nesting. But add another conditional or loop inside the while, and it's too much. Now, sometimes you need something like a namespace, which adds another pair of braces: namespace { ... }. That doesn't count as a level of nesting. The namespace increases the indentation, but it's not increasing the code complexity. Indentation isn't the problem. The problem is that deeply nested loops and deeply nested conditionals are hard to follow. ## Keeping Things Synchronized is Bad Let's say I have an enum: ``` enum ShapeType { CIRCLE, SQUARE, TRIANGLE } ``` Then, somewhere else, in a completely different file, I have this: ``` const char *ShapeString(ShapeType s) { switch (shape) { case CIRCLE: return "CIRCLE"; case SQUARE: return "SQUARE"; case TRIANGLE: return "TRIANGLE"; } } ``` Now I have to keep these two synchronized. If I add another shape, I have to add it in *two* places. Humans are terrible at remembering that they have to update two places: I honestly think AIs aren't any better. You can make this a lot better if you put the 'ShapeString' function *right* next to the enum. If you can put them right next to each other, then anybody who edits the enum will also see that they have to update the ShapeString function. ## A Recap of My Software Engineering Rules 1. Always write a block comment with the API docs before writing a function or module. Make it the greatest API ever: make it super easy-to-use. 2. After writing the API docs, write the code. Then, begin a process of iteration in which you compare the docs and the code, fixing both of them until they match, but always prioritizing fixing the code to make the API simpler, and not settling for documenting something that's hard to use. 3. In functions, keep the nesting level of loops and conditionals 2 deep or less. 4. Don't create a situation where you have to keep two things synchronized, *especially* if the two things are in separate files.