The ultimate way to avoid this is never use a calculated field in a table. ALWAYS use a query that contains what you want, after which you can pick it up from the query. Forms, reports, VBA recordset code, and even other queries simply want a recordset as input. Tables can provide this, but so can SELECT queries including JOIN and UNION variants thereof.
It might not be the right time for your retrofit, but consider this... if you actually store a concatenated name in a table that already has the parts, you double the size of the table. If you have a calculated field in the table, you slow down table functions that do not need to use the FullName field. If you have a SELECT query that contains FullName as a computation - and you use a different query when that is not required - then you save space AND save speed. For tables with short records and few records, the difference is negligible. For tables with a lot of records, it mounts up.
I actually recommend the idea of using a function that trims & concatenates the name fields, then trims the results one more time to remove embedded multiple blanks. Although with the U.S. Navy, they decided that the correct approach was that if the middle name was blank, they wanted to see "<nmn>" in that position. Of course, with the Navy, you have that wonderful case where documents that have blank pages have to have disclaimers at the top of those pages that say "This Page Intentionally Left Blank" - which of course it isn't any more if you add the disclaimer.... but I digress.
Functions are usable in queries run through DoCmd with no special requirements, and you can even use "EVAL(function(...arguments.))" if you use the DAO.Execute method. So perhaps as a future design goal you should remember to always avoid trying to store something in a table that could be computed in a query on-the-fly. That would certainly meet your requirement that the FullName has to change if any name part changes.