Disguising and Unmasking Antipattern
x = get_something()
if x is none:
return []
elif isinstance(x, Item):
return [x]
elif isinstance(x, List):
return x
else:
raise "Unknown type"This made-up example is based on some actual agentically-written code.
The get_something() method returns Any, essentially (it’s not that obvious, but the type union might as well be Any).
There are a few things going wrong here - one being the sloppy return types, but the other being more crucial and appearing in many other forms in rather too much code in the wild.
The actual antipattern is this:
- One method creates or discovers some knowledge.
- The knowledge is not passed on to the caller or called methods
- Other functions must unmask the data to determine its type/value, or in some cases must infer what was known in the other method.
In this case the answer was rather simple – the get_something (whose name was changed to protect the original context) is capable of producing a list of Items (also a changed name), so it should return a List[Item] clearly, since having zero items or one item is a degenerate case. No unmasking required!
If there is a reason the items cannot be collected (as opposed to searching and finding none) then the pythonic way of showing that is to raise an exceptoin. That is how python says “I can’t do that.”
We see a lot of places where code is written with some unmasking of a disguised variable.
def record_entry(entry: Entry): None
if isinstance(entry, EntryType1):
record(entry.type_1_data)
else if isinstance(entry, EntryType2):
record(entry.type_2_data)
else:
raise ValueError(unhandledEntry)This indicates that the Entry type is insufficiently abstract, since3 users receiving something of the Entry type must unmask the real type in order to access its data. O
I’ve also seen cases where a higher-level function retrieves some item from a database, and only passes the items’s ID to a lower-level fucntion, which must re-retrieve the item from the database by ID.
Also, I’ve seen one routine read a file and validate it, and then pass only the path to the next method (as a string), which is passed though the call tree, where the recipients have to re-validate that it’s a valid path and re-read the file.
More extreme cases have been seen where a calculation was performed and the results of the calculation written to a database. Later another process reads the data and tries to figure out how the value was calculated.
But it’s simpler this way!
This has been offered by humans and agents. It is nearly true. It does make the function itself easier - add a return anywhere you like, and declare that the function’s result isn’t typed (Any or a union of possible types).
The problem is that this makes the work less simple for all the callers. What isn’t carefully handled by the function itself must be done by each and every caller. That is duplication, and lowers the simplicity of each caller.
Do we want the work done by the one function, or by it’s dozen callers? Which is “simpler” really?
Taking your advantage at other people’s expense is essentially unethical, and in code it is not only rude, but it creates a pattern of spreading the ‘unmasking’ duties around. Remember, the LLMs will follow your patterns (and their own) so you don’t want them learning to code unethical functions and considering it normal.
Some Q-n-A
Is the typecast the problem?
No, that’s a symptom.
What’s wrong with writing defensive method code that chesk the params?
Nothing, when the caller is an unknown actor using unsanitized code
** But My method can return multiple things!
That MAY be a problem, but it may be the least-worse choice. Think hard on how you can avoid masking the type of the return and forcing all your callers to unmask them.