The method is called Send. It returns nil. The message is in a queue.

Whether it was sent is a different question, and the method name does not mention it.

Three weeks later the queue backs up. Messages sit for four hours. The caller assumed Send meant delivered. The assumption was reasonable. The name says send. The return value says success. Nobody reads the implementation of a method called Send to check whether it actually sends anything.

The postmortem will blame the queue. The queue did not give the wrong name to a method that enqueues. Somebody did that, deliberately, and the name they chose was the right call.


Three kinds of lie, each with a reason to exist.

The Send that enqueues. The method writes to a queue and returns. Delivery happens later, or never. The name Send encodes intent, not outcome. The caller who needs acknowledgment needs a different contract, but the signature does not surface that. error means the write failed, not the delivery failed. You discover the distinction in production.

The Delete that soft-deletes. The record is still in the table. The deleted_at column got a timestamp. Every query that forgot WHERE deleted_at IS NULL now returns data the caller thought was gone. The name Delete means removed from the application’s view, not removed from storage. The implementation is correct. The name is misleading. A more accurate name, SoftDelete, leaks the storage strategy to every caller that should not care whether deletion is hard or soft. It is more accurate and worse.

The Get that caches. It checks the local cache first, falls back to the database, and serves stale data if the cache has not expired. The name Get means retrieve the value associated with this key. It does not mean retrieve the current value. The distinction matters when the caller is a payment system and the cached value is the account balance. The method is fast, correct by its own contract, and the name is what anyone would call it.

Each of these names was chosen by someone who knew what the method actually did. Each name is the natural one. Each one encodes an intent that the implementation does not quite deliver. The gap is not incompetence. It is structural.


Method names are chosen at a level of abstraction where the distinction does not seem to matter. The name is written before the implementation, or borrowed from an interface that predates this specific implementation, or copied from the previous codebase where it meant something slightly different. By the time the downstream code inherits it, the name has become a fact. The caller writes logic on top of it. The unit test mocks it and returns nil. The system looks correct until the queue backs up.

The obvious fix is accurate names. EnqueueForAsyncDelivery. MarkAsDeleted. GetFromCacheOrSource. Each one tells the truth. Each one is worse design.

The name is part of the abstraction, and the abstraction’s job is to hide implementation details. EnqueueForAsyncDelivery commits the interface to a specific delivery mechanism. It leaks the queue to every caller. It is three words where one worked. When the implementation changes, the accurate name becomes a lie in the opposite direction, and changing it breaks every caller. The accurate name was less stable than the inaccurate one.

There is a deeper tension. Method names serve two readers. The caller wants to know what will happen. The maintainer wants to know what this code does. A name that is accurate for one is misleading for the other. Send is a good name for the caller. It says what matters: you give me a message, I handle it. EnqueueForAsyncDelivery is a good name for the maintainer. It says where to look when the message does not arrive. The same name cannot serve both readers equally well. The lie is load-bearing.


The shortage of shared fields exists between the caller and the callee. The method name is the shared schema. When the schema encodes intent and the implementation encodes mechanism, the join fails silently. You do not get a compile error. You get a postmortem.

The fix is not better names. It is reading the implementation when the contract matters. The name is lossy compression. It is supposed to be. The problem is not the compression. It is forgetting that compression happened and writing code that depends on what was lost.

The Send method did not lie. It compressed. Your code decompressed wrong.