4 ms·
Totally agree with you. Especially about point 1. Codegen is one of the bluntest tools there is and should be almost always avoided.
by rowanG077 4y ago
Totally agree with you. Especially about point 1. Codegen is one of the bluntest tools there is and should be almost always avoided.
- mbrodersen 4y agoCodegen is a super power. It has consistently made me 5 to 10 times more productive compared with experienced developers I have worked with. I have more than once implemented production code where 80% of the code is generated. With only the biz logic hand coded. Including a complex 50+ screen Typescript application developed from scratch in just 3 months. However, as with any other tool, you have to use Codegen intelligently to get the benefits. I have more than once seen Codegen being used really badly. Making the people using it think that Codegen is always a bad idea. Codegen is a complex tool and there are many ways to use it incorrectly.
- jitl 4y agoWhy is that? I’ve done a few toy attempts to build out ORM-like model classes in Typescript that wrap an input schema type using typelevel features, and my profiles of the Typescript compiler show a lot of slowdowns coming from combinatoric explosions in the typechecker. In this language specifically, the type system has quite advanced expressiveness but using those features in practice runs into limitations of the checker’s implementation frequently, and more so once you have many input types. We have ~80 table types and often a single table type is a union of variants - our biggest, the type for a Notion block is a union with 64 variants. I wrote a type that takes such row type as input, and outputs an OO-style Model interface for it with accessor methods, relational shortcuts, etc, and a matching Proxy that does the same at runtime. It works, and for smaller types seems like a fine solution. But in practice all the magic gums up Typescript’s inference and makes the language service crash into memory limits. Or the compiler just gives up and says “this union type is too complex to represent”. If I move the same type magic into codegen, the compiler is much happier; plus stack traces lead directly to a simple method implementation instead of jumping into difficult-to-follow proxy machinery. The downside is that the codegen logic that consumes the Typescript compiler API to understand the schema types is more complex than the mapped types before, but it does make it feasible to have my cake (derive an ORM/model API from a schema) and eat it too (not make the compiler so unstable and touchy).
- spion 4y agoWhile in that specific case codegen does sound like the right approach, I think in most regular cases you would be able to get away without it. Honestly, a 64-variant type is not really that common. I think codegen is OK as long as - its designed in a way where it doesn't cause users to want or need to modify the generated code (this is the biggest downside of codegen) - its against "relatively stable" API thing (protobuf / swagger descriptions, DB schema etc).
- rowanG077 4y agoI said almost never not never, 64 variants is quite esoteric I'd say. The reason why I dislike codegen so much is that you fix one problem but introduce multiple new problems. How do you handle the generated code? Do you check it into source control? If so, how do you ensure people do not touch it. Manually modified code generated code is one of the worst places to be in for maintenance. You mentioned you wanted language server support this means the generated code must not be ephemeral and continually visible to the rest of the project. When will this generated code need to be regenerated? On a schema change? How do you ensure the generated code always matches the schema? Like I said all of these question can be answered, but it's quite a burden. In fact if I were you I would have looked into fixing the typescript compiler since the problems your ran into probably weren't fundamental. -
- jitl 4y agoMy general tactic for codegen hygiene like this is to check in the generated files, and on every CI re-run the code generator. After the generator runs, CI asserts there are no changes in Git. If there are any changes, the job fails. This ensures that generated code is consistent with its inputs (including the generator logic) for every merge to main. It prevents people from editing the generated code, since their edits will cause a diff in CI and fail the build, but no one ever actually loses their work since the codegen does not run automatically/continuously during local development. Checking in the files also makes it very easy to review changes to the generator since you can always tell how the output is changing. The total burden for the above system is: - a 30 line command called `Notion assert-clean` - A 30 line CircleCI job that loops over the list of code generator commands, calls the command, then calls assert-clean. We’ve used that tactic for years for simpler stuff like “make sure the SQL dump is consistent with the SQL migrations” and “put all the file names in this directory tree into a typescript file so we can tab complete them”. The only necessary bit is that your code generator shouldn’t be spitting out enormous mountains of unreadable code.