Someone else will open this script on a bad day
The script you write this afternoon inside a ServiceNow instance or a Power Platform environment will be read by someone who has never met you, probably during an incident, probably in a hurry. That person will not have your notes, your ticket, or the conversation you had with finance back in March. Everything they can recover about your intent has to be sitting in the thing you shipped.
I inherited a contractor onboarding catalog item at a logistics firm two years ago. The behaviour lived in four places: a client script on the form, a server side business rule on the request table, a flow that fired on approval, and a scheduled job that cleaned up what the flow missed. One condition skipped the second approval whenever the requesting person's cost centre began with the digits 41. No comment, no ticket reference, nothing in the release notes. It took me three weeks and two former employees to learn that a finance lead had made that call in 2019 for a division that no longer existed. We deleted it. Nobody could have worked that out from the code.
Most of the logic inside these platforms was never written by a software team and never reviewed by one. That is not a failing of the people writing it. The platforms are built so the admin who understands the process can write the rule herself, which is usually the right call. The habits that keep that code alive have to come from the person writing it, because no reviewer is coming.
The why is the part that disappears
Comments describing what a line of code does are close to worthless. Anyone competent enough to be reading your business rule can see that it sets the assignment group when the category is hardware. What they cannot see is why hardware gets different handling, who asked for it, and whether that reason still holds. Write that down, in two sentences, right above the block that does the strange thing.
The format I use is plain. Which rule this implements, who owned the decision, and roughly when it was made. A note saying the second approval is skipped for the old European division because finance signed off during the 2019 process review gives the next person something to go and check. Without it, they choose between leaving logic they do not understand in place forever and removing it to find out the hard way. Ticket references help, but only while the ticketing system holding them still exists, so write the reason in words as well.
One rule belongs in one place
The most common maintainability problem I find is the same rule written twice: once in a client script so the user gets immediate feedback, and once in a server rule so the API path is covered. Both copies are correct on the day they ship. Six months later somebody changes the threshold in the client script because that is the one they found, the server rule keeps applying the old number, and nobody can reproduce the bug from the interface.
Where the platform allows it, put the decision in one callable place and have both entry points ask it the same question. A script include in ServiceNow, a single method in a plugin, a child flow in Power Automate. Where the platform forces you to duplicate, say so in both copies and name the other location, so the next person changing one knows a second exists. That line costs nothing and saves the afternoon somebody would spend wondering why their change had no effect.
No single screen shows the whole behaviour
The trap specific to these platforms has little to do with how complicated any one script is. Behaviour gets spread across execution points, and no screen anywhere brings them together. A record in ServiceNow can be touched by a client script on load, a change script on a field, a business rule running before the write, another running after it, a flow, a scheduled job, and an inbound integration, all inside the same minute. Power Platform has the same shape, with synchronous and asynchronous plugin steps, cloud flows, and rules configured on the table.
Neither platform will draw you that picture. You assemble it yourself, one execution point at a time, usually while something is broken and someone senior wants an estimate. The habit that helps is deciding where a behaviour lives before you write it and then keeping it there. If a field has to be calculated, calculate it in one place and let everything else read the result rather than working it out again in its own way.
Decide what happens when the data is wrong
Custom code inside business platforms fails quietly by default, and that habit does more damage than anything else here. A lookup returns nothing, the variable stays empty, the script carries on, and the record saves with a blank field that nobody spots until a report comes out wrong a quarter later. In almost every case I have traced, the author knew the lookup could miss and never said what should happen when it did.
Handle the miss out loud. Decide whether the right answer is stopping the transaction, filling a documented default, or writing a record somewhere a human will read. All three are defensible. Staying quiet converts a visible error into a data problem that surfaces months later with no trail back to the cause. The same standard applies inside the platform as on the integration side, where error handling that pages someone separates a two hour outage from a two week reconciliation.
Record identifiers that do not survive the move
Hard-coded record identifiers are the most reliable way to build something that works in the environment where it was written and nowhere else. The approval routing in that onboarding item pointed at a group by its internal identifier, copied out of the production instance. When the team rebuilt a few groups in a sub-production instance, the identifier matched nothing. The rule threw no error. It assigned approvals to nobody, and eleven contractor requests sat in a queue for nine days before anyone asked where they had gone.
Look the thing up by something a person chose and a deployment preserves: a group name, a role name, or a value each environment sets for itself. Power Platform gives you environment variables inside the solution for this, and ServiceNow gives you system properties. Searching your solution for stray identifiers takes under a minute and belongs in the release routine you already run, because the ones that slip through arrive during late fixes.
The person reading this at two in the morning
Every piece of custom logic should leave enough of a trace to answer one question during an incident: did this run, and what did it decide. Not a debug log of every variable, which nobody reads and everyone switches off. One line when the logic does something that matters, carrying the record, the branch taken, and the reason. In ServiceNow that can be a single system log entry with a prefix you can filter on, and in Power Platform a row in a table you own. That line turns a thirty minute guess into a thirty second check while the first thirty minutes of an incident are burning.
Before you save the next rule you write, read it once as though you have never seen the instance. Check whether it says why it exists, whether the same decision gets made anywhere else, what it does when the lookup comes back empty, and whether it will still find what it needs after a deployment. If the honest answer is that the rule should never have been code at all, where business logic should live is the argument to have before you write the next one.



