This page will present a version of the document I used at Question as its Tech Lead.
Permission to use this on my portfolio was granted by my gracious former business partners.
A lot of solo dev practices can be harmful in a team environment.
This page exists as a means of self-auditing your Blueprint before it gets reviewed or audited by someone else.
<link to Blueprint Audits page on Confluence>
If you are the reviewer/auditor, this is a list of things to look for during the review.
Each pattern on this page will be in the following format:
There is an engineering concept called software development anti-patterns that programmers are often forced to learn as part of their education. This notion of knowing what to run towards AND what to run away from helps paint a mental silhouette of best practices within the studio.
The goal is to avoid scripters copying each other's bad patterns to the point that it starts becoming the norm (see Broken Window Theory In Software Development). Bad patterns tend to be fine in a solo-dev environment, but in a team, they make it very difficult to maintain complex logic (with your future self) and load balance with others in sustainable ways.
The problem you are most likely trying to solve is to make something happen upon some future condition.
Why is it bad?
These can introduce hidden breakages in logic. When you rename or delete a function, the editor will help you catch any uses of that function anywhere in BP that need to be updated. If all the editor has to work with is a string, then the only way to catch the problem is through QA noticing that something is not working.
What to do instead
In General, you should be using the "-By Event" Functions instead of "-By Function Name" Functions. Those pass in a named Delegate so that when a function is renamed, it will either successfully fix the name, or will throw up an error that will be caught immediately instead of depending on QA to discover a bug.
The problem you are most likely trying to solve is to find something you are looking for in the map.
Why is it bad?
This does a for loop for ALL Actors in the level. If your level has a thousand actors in it, then that for loop can go through all of them before the next tick. This is pretty much a guaranteed frame hitch.
What to do instead
The alternative is to use a register/unregister pattern where actors register themselves to an array with some central manager (a component you made attached to GameState), and then you search that array instead of using this.The Goal system also provides a way to gather actors based on GoalMarkers and tags.
The problem you are trying to solve is to do different things based on what the object is.
Why is it bad?
If a new thing gets added by a person, that person might not know to add their new thing to your chain of tests in some other file that they may not be familiar with. Remember that one of our goals is to make yourself replaceable so that others can load balance you or help fix your stuff as needed.
What to do instead
Object-Oriented Approach: Move the burden of knowing inside your class instead of having script outside the class do different things based on type:
If the class is a C++ class, then make a Base class in BP that derives from the C++ class and then derive all variants from your base class.
Or if there is no one base class to rule them all, consider adding a BP interface: Blueprint Inter face | Unreal Engine 4.27 Documentation | Epic Developer Community
Overall, the goal is to make sure each new thing you add to the game requires touching the fewest possible number of files. Another way of looking at this is to imagine your new thing is part of DLC. When adding DLC, you want the base product files to remain untouched. DLC downloads would be made up of 100% new files, and no existing files should be redownloaded by the user when they get that DLC for additional cosmetics or new weapon or enemy types.
The problem you are likely trying to solve is that a thing happens too soon, but if you spin and wait until some condition is met, then the bug goes away.
Why is it bad?
It prevents the classic “order-of-initialization” problem from being solved the correct way, which will lead to a proliferation of this pattern throughout the game, which can lead to delays taking much longer than necessary because something is waiting for a delay loop to finish somewhere and it may also be doing a delay loop waiting for something else to finish… delay loop centipede!
Imagine a situation where A depends on B, and B depends on C and all 3 of these are doing delay loops waiting for each other.
It can also lead to situations where stuff is happening when it is not supposed to happen because the delay loop can continue and will not be canceled even after the situation is no longer relevant. Imagine starting something that starts this loop and then ending that something while the loop is still in progress. You can fix it, but now your script will look even more spaghetti then before compared to solutions based on event bindings.
These overlapping inter-dependent delay loops generally lead to non-deterministic repro steps in which bugs only happen when the sequence of actions happens in a very specific order governed by chance based on how fast that particular user's hard drive is in relation to the CPU and network connection they are playing on.
What to do instead
Bind to events, such as ReadyForUI, ReadyForGameplay or anything on this page: <link to Safer alternatives to BeginPlay in Confluence>
Work with engineering to provide the events you need.
The best thing engineering can provide for you is a BP Wait node that waits for exactly what you want and moves on as soon as the requirement is satisfied:

The problem you are most likely trying to solve is to make sure stuff happens only during a specific situation.
In the image below, each of these bools can be thought of as mutually exclusive, meaning the logic is geared towards making specific things happen when 1 bool is true while the rest are false.
Why is it bad?
This usually starts out innocently enough, with just a few bools, but as more bools get added, the chain gets longer and this complexity becomes harder to manage for both, your future self and other team members.
What to do instead
If only 1 bool is true at a time, then you basically have a state machine. The moment you find that you have 2 mutually exclusive bools is the moment you should consider converting your logic to state machine logic. This impulse should become stronger with each bool that gets added to the mutually exclusive pot.
We have a dedicated page on doing state machines in Blueprint: <link to HOW-TO Use State Machine Thinking to Manage Blueprint Complexity in Confluence>
tldr; replace those many bools with a single enum.