In our code base, we have different types of errors. The simplest is, for example, when the user creates an incorrect model, an error is raised and reported in a friendly way for the user. For all other unforseen situations we raise an exception. When the user runs the product, all these exceptions are catch and they display an “internal error” message. Since we have a single point where this exceptions are catch, it is very easy to disable the catch. So we have a flag that will make our program not catch the exceptions when debugging. When there are uncaught exceptions, ocamldebug stops in the place where the exception was thrown. Then we just have to back step a bit to figure out which conditions lead it to raise the exception.
<poly> makes more difficult to see the values in the debugger. To debug these cases I usually create data printers, either with ppx_deriving when possible or if I need something more simplified I use a small library I made called Pla which helps me create printers in a simple way. For most data structures we have some kind of printer. I usually place a print at the place I want to see and recompile + rerun the debugger. In ocamldebug you can see the “time”, I copy that value and in my rerun I just put a
goto time or a
break to go back to the place where I was before.
We do not use
threads and we don’t use too many external libraries. Initially, our code base depended only on the standard library.The main reason was that, back when we started with OCaml, it was difficult to get many of the libraries to compile in Windows using the VS port. Since we converted all our code, that included all the libraries an utilities we required. As the time passed, we started integrating other libraries, for example
dune. Those are probably all the libraries that we depend on for building the product. In the development computers we install
Other trick we use to simplify debugging is that we have organized the code in a way that it allows multiple entry points. For example, our compiler has different stages:
stage1 -> stage2 -> stage3 -> stage4. If we have detected an error in
stage4 we can easily export the input data to that stage (using
Marshall). Then we can run only the
stage4 though it’s custom entry point. That way we skip the stepping of the debugger on all the other stages which can be slow.