Yes, I know it's possible to make exceedingly bad code with precompiler macros, but you hardly do that by mistake. Correctly used, they are great.
Example (very simple, and not so practical, but practical examples would be too complex as precompiler macros are best used in very complex situations):
In this case, you could have replaced it with a function call. However, consider this situation:
DoLotsOfStuff is a huge sub, which does lots of calculations on several huge sets of data. In these calculations, some bits are recurring frequently. However, they use much of the datasets, so you can't cleanly turn them into subs, because you don't want to pass 20 large multidimensional arrays as arguments. Instead, you make a macro, so you get something which looks almost like a function call, but instead inserts the code inline, with whatever parameters you need. Neat, eh?
Another advantage is that, since it's just a string replace, you can use it to replace things normally not replaceable, such as method names and variable names. Sure, CallSub does some of this, but here, as it's done before compilation, you get it checked by the compiler, instead of bombing at runtime.
Example (very simple, and not so practical, but practical examples would be too complex as precompiler macros are best used in very complex situations):
B4X:
Macro DoSomeStuff(Param1, Param2, Param3)
Param2.Param3("Param1")
End Macro
Sub DoLotsOfStuff()
InsertMacro DoSomeStuff("Performance", "Log", "Started") 'Insert the macro, doing a text replace to insert the parameters before compilation
End Sub
In this case, you could have replaced it with a function call. However, consider this situation:
DoLotsOfStuff is a huge sub, which does lots of calculations on several huge sets of data. In these calculations, some bits are recurring frequently. However, they use much of the datasets, so you can't cleanly turn them into subs, because you don't want to pass 20 large multidimensional arrays as arguments. Instead, you make a macro, so you get something which looks almost like a function call, but instead inserts the code inline, with whatever parameters you need. Neat, eh?
Another advantage is that, since it's just a string replace, you can use it to replace things normally not replaceable, such as method names and variable names. Sure, CallSub does some of this, but here, as it's done before compilation, you get it checked by the compiler, instead of bombing at runtime.