Hardware Side:
Use an automotive grade supply module to save a lot of the difficulties of load dump and other issues if your current is small, it also makes certification faster, Recom has been great to me so far
Treat every connnection on your device as if it will be hooked up backwards across 24V while a 5W test light probes the other connections, (I use level converters for a lot of my IO)
Those input diodes do result in side effects, if its to your VCC rail, is your load large enough to keep the voltage in check if every IO was connected to max voltage? this is sometimes called backdriving, where your powering your device through the IO,
Don't trust the ground too much, a lot of older trucks and buses may have 2V offsets between electrical and chassis ground, some due to age, and some due to negative isolators, (These are also why I design around -24V, with some negative isolators it ends up biasing the chassis to -24V of the permanent electrical negative)
if your using a potentiometer for some control input, make it ratiometric, this way noise gets mostly cancelled out
Use pullups / pulldowns to define the disconnected state of inputs, and if something is important, detect if its actually connected, this might mean 2V is normal idle, and 5V is your signal high, so 0V or 12V you know there is an issue
If you don't care about the common mode voltage, consider AC coupling the signal, things like frequency inputs etc,
Keep larger gaps between permanent supplies and ground, corrosion around where a battery pin is surrounded by a ground flood are pretty common,
Be careful about how much your loading external signals, the last thing you want is for your device to change the behavior of some other device (ticket machines have a 10mA pullup on there speed signal which messes with a lot of systems)
For the devices I had to design for very noisy environments I used a "demagnetiser" basically a cut in half mains transformer hooked up to a PA system, to sweep all kinds of noise over the PCB from various angles to try and induce any kind of misbehavior,
If you have room on the board, label the connections, it makes debugging much faster to see that pin 43 is a clock, or the input range is 7-30V when your supporting something you designed 5+ years ago
Break out unused IO to pads, they don't have to be big pads, but enough that if something changes you can bodge a wire onto it for field testing until the next revision comes out.
Software:
Define the states your system can be in, in an ideal world if your watchdog just fired you will be able to take that state and pick up were you left off with no visible change to the user, (A watchdog does not have to just reset the system, it can call a cleanup interrupt function or similar)
Figure out what valid and invalid data looks like from your sensors and bake that testing into your library, if your expecting 42 bytes within some range, did you get 42 bytes, and was it in the range? obviously it depends on how critical the data is, but as a designer you will have some idea, if a read failed, well that's a state your system can be in, and you need to define it, same for if writing to a sensor fails etc, it might seem overkill, but if a cockroach has pooped on your SPI clock and data lines, your going to need to have some awareness that a problem has occurred.
some approaches are retry X amount, others are discard the result and defer the code that needs the result until some timeout, and if it cant get a result by some amount of effort, assume some safe default and handle the state. if its some option you dont care about, then have a branch for it saying as much
Class Enums, Constexpr and Static Asserts are great, the main trick is try and isolate the physical parts from the logic parts, this way you can test that 2+2=4 be it on the micro or on a PC, Enums help with making sure only the intended places can use or update the values, Constexpr lets you offload as much as reasonable to compile time, and with it you can then do compile time checking that functions are working how you expect,
Specify sizes for your variables and functions rather than int or long helps with compiler warnings such as unintended conversions, alternatively you can use auto if code size is not as much of a constraint, though I prefer to be specific personally,
Most libraries arent always that well made, I'll generally take ownership of the library for a given project, e.g. have a local copy or remake it specific to the purpose, for things like SPI or I2C peripherals its generally a hour or 2 to make tops, and means you have vetted that it is in agreement with the datasheet, (e.g stripping down software serial for write only takes it from 600 bytes of memory to 80)