There are few tasks a developer enjoys more than writing documentation. It’s a thrilling journey into the exciting world of code formatting and variable naming conventions, right up there with untangling someone else’s regular expressions. So, you can imagine my sheer, unadulterated joy when I was asked to create a "Best Practice" document for my team's C# projects.
![]() |
C# |
- C#: Has a garbage collector, which is like a butler who tidies up after you. It’s convenient, but you still need to know the rules. You have to explicitly tell the butler about any special (unmanaged) items using IDisposable, otherwise, they'll be left lying around.
- Arduino/C++: This is the survivalist end of the spectrum. You have a tiny backpack with 2KB of RAM, which is less memory than a high-resolution emoji. Every byte is sacred. Heap allocation is a dangerous game of Jenga that leads to fragmentation and mysterious crashes. The Arduino String object is a notorious trap for new players, munching on your limited memory. Here, best practice isn't just a good idea; it's the only thing keeping your project from collapsing.
- Go: Also has a garbage collector, but it’s more of a silent partner. The language and its idioms are designed in such a way that you rarely have to think about memory management. It just works.
Cerberus
Cerberus-X: As another high-level language, Cerberus-X handles memory automatically. The developer's main responsibility isn't freeing memory, but ensuring its state is predictable. The most crucial best practice is to always use the Strict directive. This is the "no more mystery values" setting, as it enforces that all variables must be initialized before use , saving you from the bizarre bugs that come from variables defaulting to 0 or an empty string in non-strict mode.- Lazarus & FreePascal: The "Choose Your Own Adventure" Model
This is where things get really interesting. FreePascal offers a mixed model for memory management, letting you pick the right tool for the job.
- The Classic Approach: This is pure manual control. Every object you create with .Create is your responsibility, and you must personally ensure it is destroyed with a corresponding .Free call. The try..finally block is your non-negotiable safety net to guarantee that cleanup happens, even when errors occur. It’s the ultimate "you made the mess, you clean it up" philosophy.
FreePascal cheetah
The LCL Ownership Model: The Lazarus Component Library gives you a helping hand, especially for user interfaces. When you create a component, you can assign it an Owner (like the form it sits on). The Owner then acts like a responsible parent: when it gets destroyed, it automatically frees all the child components it owns. You should not manually .Free a component that has an owner.- The Modern Approach: To make life even easier, FreePascal supports Automatic Reference Counting (ARC) for interfaces. When an object is assigned to an interface variable, a counter is incremented. When that variable goes out of scope, the counter is decremented , and once it hits zero, the object is automatically freed. This brings the convenience of garbage collection to your business objects, drastically reducing the risk of memory leaks.
- C#: async/await feels like delegating a task. You ask a subordinate (Task) to do something, and you can either wait for the result (await) or carry on with other work. It's efficient and clean.
Go
Go: Go's model is more like an automated assembly line. You have multiple workers (goroutines) and a system of pneumatic tubes (channels) connecting them. Workers perform their small task and send the result down a tube to the next worker, all happening simultaneously.- Arduino/C++: You're a solo act on a mission. There are no threads, so you can't do two things at once. The entire game is to never stop moving. You check a sensor, update a light, check a button, and repeat, all in a lightning-fast loop(). A delay() is your worst enemy because it brings everything to a grinding halt.
- Lazarus/FreePascal: This is the classic office worker. To avoid freezing the UI during a long operation, you spawn a TThread to do the heavy lifting in the background. When the worker thread needs to update a label on the screen, it can't just barge in. It has to use TThread.Synchronize or TThread.Queue to politely tap the UI thread on the shoulder and ask it to make the change safely.
- Cerberus-X: This is the resourceful indie developer. It doesn't have the fancy built-in machinery of async/await. To achieve non-blocking operations, it falls back on the fundamental tools, letting the developer build their own solution using threading or designing methods with callbacks.
- C# & Friends: Languages like C#, Lazarus, and Cerberus-X prefer the "town crier" approach of exceptions. When something goes wrong, they shout about it loudly, and a try...catch block is expected to handle the commotion.
- Go: Go has trust issues. It prefers you to look before you leap. Functions return an error value alongside their result, forcing you to confront the possibility of failure at every single step.
Arduino C++ - Arduino/C++: When your code is running on a chip in a field, how does it cry for help? It uses a smoke signal. There's no console, so robust error handling involves returning status codes or, in a critical failure, entering a safe state and blinking an LED in a specific pattern—a primitive but effective "blink of death" to signal an error code.