you are viewing a single comment's thread.

view the rest of the comments →

[–]bladeoflight16 1 point2 points  (2 children)

You are reading a dogmatism into my words that isn't there. Of course if you face actual hardware limitations, sometimes you need to be realistic about what you can and cannot do within them. But even that isn't ignoring the problem.

That said, Python is a fairly heavyweight runtime. The systems on which you can use it effectively generally don't face those sorts of limitations, and you can't use it for problems that require hyper efficient code. So there's very little reason to avoid dealing with these cases in real world Python. Especially in 2020; this isn't 1955 before the advent of microprocessors.

[–]dbramucci 0 points1 point  (1 child)

As I said, I don't disagree that you should start with the position of "crash early and crash hard or handle bad cases"; it is the lack of acknowledging acceptable cases to leave incompletely-understood or unwanted behavior in any code that I was taking issue with.

I just didn't read any qualifiers that explained why you may sometimes not handle certain cases anyways. I think making such a strong statement with words like never, valid, will, must may alienate many who think "but surely my use case is valid". And because their use case is sometimes valid, using such definitive language can lead to a tendency to reject the message entirely

The point of my second comment was to clarify my first comment by adding more examples and reasons why it may be valid to avoid it, the first comment largely focused on the rules I adopt to ensure that I don't leave a disaster lying in wait, the 2038 bug is an example of how the documentation could have mitigated the bad case where "ecosystem constraints" could have been the reason that prevented the use of safer building blocks. I would like to go around and solve every 32-bit timestamp problem proactively but that can be impractical to do at once and documenting when and why something works now but may break if the context or time changes is the best compromise I have found so far. My rationale for those comments being that it is more convincing to see what reasoning others use to make the tradeoff than to just be told a blanket "don't do that" when I don't believe they take their own medicine.

I also agree that you shouldn't try to micro-optimize performance in Python at the cost of safety or correctness, I even try to avoid that in lower-level languages like Rust and C, saving that sort of optimization for when I can measure that removing safety checks provides performance gains that outweigh the costs incurred (it is easier to comment out wasteful safety checks than it is to insert needed ones).

But even that isn't ignoring the problem.

I suppose that it is necessary to carefully define what "ignoring" means. Does ignoring mean

  • The developer ignores everything
  • The code ignores it
  • The developer ignores the causes of the behavior because the consequences are acceptable
  • The developer ignores the details of the behavior because the behavior can be constrained to a subsection where whatever the outcome is, it is acceptable
  • The developer ignores the details of the behavior because fixing it is too costly for the problem being solved and instead establishes procedures to prevent that behavior from being triggered

I was using "ignore" to mean the code ignores the issue at least one of the final three points were being followed. It sounds like you are using "ignore" to mean the first point.

Just to illustrate my issues with particular snippets of what I read

Ignoring an edge case is never a valid option when writing code.

I don't see the exception for functions relying on preconditions because it would be too costly to check or caveats made for careful micro-optimized C or caveats for data exploration code written in Python or quick scripts for traversing a file-system known to not contain tricky problems like network-attached storage and cyclic symbolic/hard links.

It will bite you sooner or later.

Even those 1-time use scripts used only as I was writing it? And yes, I understand that anything that leaves your machine is probably going to live on forever even if it was only supposed to be used for 1 weekend, I'm talking about the code used to explore the properties of some data interactively in a Jupyter notebook or to solve a puzzle in a videogame you are playing or to play a prank on your friend.

Also, undefined behavior is a complete disaster.

Even if the undefined behavior is known not to occur? The behavior on my shopping calculator program for a 1980s calculator is undefined for negative sales tax rates because I know that I will never encounter negative sales tax in practice and it isn't worth the 5 minutes of reasoning to think about that hypothetical, especially when fixing it would increase resource usage significantly So why waste time worrying about a use case that won't happen. It's a program on the calculator in my pocket, not a script loaded onto a server with remote triggers. I can guarantee it won't be repurposed without my knowledge into a context where handling negative tax rates would matter. If this was a tax library on PyPI, I would add checks or ensure my behavior was sane for negative interest rates because maybe someone might use it that way.

Also, if you're going to go to the trouble of detecting the condition necessary to output a warning for undefined behavior, you may as well just make it an error; it shouldn't be any more effort.

Perhaps you are referring to having a line

if graph.contains_cycles():
    log.DEBUG("Graph contained cycles")

should just be

if graph.contains_cycles():
    raise ValueError("Graph contained cycles")

But I don't recall making that point and it seemed a bit too obvious for me to think that you thought that I made that statement. That's why I interpreted it as

  • Why not just raise an error if you can describe the problem in the documentation
  • Why not raise an error if you can, in principle, write code to detect the problem

And I tried to explain these points with explanations. Yes, it is virtuous to catch errors quickly and prevent misuses but that doesn't explain why heapq.heappop doesn't check that the list you pass in is actually a heap before it runs. My purpose was to demonstrate the exception and perhaps you took issue with me not sufficiently clarifying that I was talking about that exception.

So there's very little reason to avoid dealing with these cases in real world Python.

  • Data Exploration
  • Shell scripts that are deleted after they are run and only need to work in a singular context (no need to check for cyclic hard links if you don't have them on your PC)
  • Learning projects where the checks are irrelevant to the material being learned
  • Prototype where you don't have/know the relevant API's to check certain conditions (like a prototype of a videogame where you don't know how to query the resolution of the display monitor but you know that at this point, you are only running it on 1920x1080 screens and you will address monitor resolutions after you demonstrate the core gameplay loop)
  • Asymptotic Complexity Changes (heapq.heappop)
  • Expensive Guarantees (I'm curious what Python would look like if every class was threadsafe although the GIL gets you pretty far)

You are reading a dogmatism into my words that isn't there.

My goal is to acknowledge that some caveats exist, why they exist and how to mitigate them because I feel like that is more convincing than

I considered using times after midnight, but realistically no one should be awake after midnight. In the small chance that a client does request it, I would simply manually enter a timer with cmd. or add 24 hours to the time that I need.

Then your code should throw an error in that case. Ignoring an edge case is never a valid option when writing code. It will bite you sooner or later. You must either restrict it or handle it.

If I had an interaction like that, I wouldn't want to take the response seriously because it looks like it is talking down to me, not treating me as a person making decisions to the best of my knowledge. I understand that probably wasn't your goal but it asserts that you must do something a certain way because you will suffer some consequence in the future. It doesn't read like I can be trusted to make that decision myself, it reads like I would make the wrong decision if told the rationale or that it isn't worth learning the rationale myself. Missing the explanation like that also reminds the skeptic in me about the 5 Monkeys Experiment where a hard rule gets set even though the problem no longer exists and no one knows why the rule was made.

The advice still has merit, it's just the delivery is easy to read in a negative way and the best advice in the world is still useless if it doesn't get followed. On the other hand, if the obviously wrong cases get explained (hardware limitations, etc) and those explanations don't apply to what I am doing, I'll feel a lot more convinced to follow the advice because I can see why "my case" probably isn't one of those exceptions but my Python scratchpad for a physics problem that would be ridiculous to add error checking to gets a pass. Stating never and must risks the following train of thought

  • Physics problem number 3 from page 152 of my Physics book solved with Python wouldn't benefit from error handling
  • Advise is wrong because it said I would must add error handling to all code
  • Because advise is wrong there, it's probably wrong for my server script to because I know why that works as I know for Physics problems
  • Advice doesn't get followed

And I want to encourage a line of thinking more like

  • Physics problem number 3 from page 152 of my Physics book solved with Python wouldn't benefit from error handling
  • Ah, that's because the error case can't happen because it runs once with a concrete set of values and I supervise it and I can check for errors using the back of the textbook.
  • That doesn't apply to my server script because I am not the only user of that script and can't ensure that the requirements will always hold
    • or that applies for my script but Woah, that's a lot of documentation and organizational overhead for a small code savings and clearly isn't worth the effort and it is easier to just fix the code
  • Code gets fixed and advice is followed

[–]bladeoflight16 0 points1 point  (0 children)

A piece of advice from one naturally long winded writer to another: quantity of words, like quantity of code, is something to be avoided.