Hacker News new | ask | show | jobs
by zeugmasyllepsis 2584 days ago
> ...would have told them immediately at compile time that the last_name field access by other client code was broken.

What's the proposed fix in this case? Should all clients accessing that field switch to `customer.full_name`? Do you control all of the call-sites? If so, that change is pretty easy to automate in dynamic languages by marking the field as deprecated, adding the new field, and forwarding `customer.last_name` to `customer.full_name`. That way, running systems don't break but you get the new behavior. You can automate logs to track when the deprecated field is accessed and, if necessary, fix call-sites after a reasonable period of monitoring, reducing risk (assuming there's not "once-in-a-century" paths in the code that invoke the call site).

It seems reasonably likely that this is not the desired behavior - you may have external clients, and some callers may be relying on this field to actually return only a last name. In that case, deprecation is still the correct path, I think, since it's unreasonable to expect external clients to immediately fix systems relying on that field being available.

Static typing can tell clients where they need to make changes, but it's not realistic demand that they immediately update every call site, every method depending on that behavior, and every integration with their external dependencies that relied on the interaction between your code and another system. This is why we have (semantic) versioning, deprecation warnings, and support agreements for larger systems. API's should evolve slowly and gracefully over time, not shift suddenly overnight.

3 comments

The proposed fix is that you don't deploy code that the type system can prove is broken, and the person performing the refactoring needs to do additional work rather than pushing out something that will fail in production.

My reading of jsode's example is in a shared codebase, where it would be reasonable for the person performing the refactoring to update all call sites, and where checking in a change that does not update all call sites to whatever branch is released to production should be unacceptable, as it will provably cause problems in production.

If the change was made in a standalone library that other teams depend on, the type-checker error would be produced when they update the library to the new refactored version, and it's the same story: prohibit deploying known-invalid code to production until you've updated all call-sites to match the changes in the library API.

If updating all call-sites immediately is implausible for whatever reason, leaving the old field in place with a deprecated annotation would be quite reasonable, but the purpose of a type checker in this context is to prevent you from failing to handle this in at least some way. As you say, responsible maintainership of an API should facilitate gradual transition; the use of a type-checker here is to prevent deploying code that will fail at runtime when someone has made a mistake, and can help facilitate responsible maintainership.

I don't disagree that static type checkers and analysis software can be useful for catching many kinds of errors. In fact, at the end of my comment I emphasize exactly their strength: in automatically detecting when and where clients need to make changes. My intent in making the comment was to highlight that static analysis is only one part of a larger solution to the problem of keeping call sites in sync with the code they depend on, and to discuss other techniques that don't rely on static analysis. I think articles and comments online focus too much on the debate between static and dynamic typing, instead of the engineering principles that apply to both camps, which can help mitigate the risks associated with change.
>What's the proposed fix in this case?

For the purposes of my reply to the author, the type of fix doesn't matter and its discussion would be a distraction away from my main point: the blog author Curtis "Ovid" Poe misintepreted the comment by Samuel Falvo II and therefore wrote the wrong "solution" as a response. Ovid's health data example of obj.fetchDataFromServer() error and reconstructing data from other sources with a threshold score is not the same abstraction level as the commenter's example of obj.last_name getting broken because the plumbing API was renamed/refactored.

(My guess is that Samuel Falvo II's hostile tone makes it easy to miss that he was actually describing a syntax error and not a data interpretation error.)

Regardless of whether the fix is:

- stringsplit(full_name) to extract a last_name

- or change the client code's usage of last_name and only use full_name

- or re-architect the upstream customer object to include both last_name and full_name so there's a gradual migration

...it isn't the issue.

What the angry commenter was trying to communicate was, "tell me about the last_name refactoring causing a syntax error _immediately_ instead of letting it sit there as a ticking time bomb that will blow up and wake me up at 4am".

He doesn't necessarily have to immediately fix it, but he does want to immediately know of the error's existence before a crash at runtime.

I apologize for being unclear. My intent was not to suggest that static type checking (and more broadly, static analysis) were not useful tools in catching errors early. Rather, I wanted to emphasize that other techniques could be used to mitigate the risks associated with change, such that the reliance on static analysis could be minimized. In that sense, both static analysis and the mitigation techniques help prevent the angry commenter from waking up to 4am alarms in prod.
So you marked the field deprecated and stated it would be removed in release "potato-wedge". The "potato-wedge" release rolls around, you delete the field, and Ted, who didn't care to pay attention to the deprecation warnings, ... does what?

In a statically typed world, Ted tries to compile his project and gets an error saying, "last_name doesn't exist". In a dynamically typed world, Ted ships his code into production (or possibly testing) and gets a call at 4:00a.m. saying it threw an exception or just died. (The testers are in Bangladesh.)

In Alan Kay's world (or at least that of the author), the code goes on robustly producing whatever results it produces in the case where no one has a last name. Better?

I agree with you! Static typing and more broadly static analysis can help catch some types of backwards incompatibilities. The intent of my comment was to highlight complementary techniques that can minimize the reliance on static analysis as the primary tool for keeping code in sync with dependencies. Alone, any one technique is not particularly fool-proof.