In Google's style-guideline documentation for python, they mention the following:
Note that this raising of ValueError is not mentioned in the doc string's "Raises:" section because it is not appropriate to guarantee this specific behavioral reaction to API misuse.
Why is it not appropriate? and does that mean the ValueError shouldn't be used like this in the first place?
def connect_to_next_port(self, minimum: int) -> int:
"""Connects to the next available port.
Args:
minimum: A port value greater or equal to 1024.
Returns:
The new minimum port.
Raises:
ConnectionError: If no available port is found.
"""
if minimum < 1024:
# Note that this raising of ValueError is not mentioned in the doc
# string's "Raises:" section because it is not appropriate to
# guarantee this specific behavioral reaction to API misuse.
raise ValueError(f'Min. port must be at least 1024, not {minimum}.')
port = self._find_next_open_port(minimum)
if not port:
raise ConnectionError(
f'Could not connect to service on port {minimum} or higher.')
assert port >= minimum, (
f'Unexpected port {port} when minimum was {minimum}.')
return port
CodePudding user response:
The ConnectionError
can be the result of using the function per its documented usage.
The ValueError
, however, is only the result of a flagrant violation of the function's precondition. You've already been warned that minimum >= 1024
must be true. You don't need a documented consequence for violating that warning.
For example, you don't need a try
statement to handle the ValueError
; you can just check the value of the argument before calling the function to avoid it. (This is a case where it is not easier to ask forgiveness than permission.)
You do need a try
statement to handle the ConnectionError
, since there is no way to predict that it might occur. In order to know that a ConnectionError
might be raised, that needs to be documented.
From the perspective of a total language with static type-checking, the difference would be errors you can avoid by using an appropriate argument type, and errors you can avoid by using an appropriate return type. Consider partial function in a Haskell-like pseudocode.
type ValidPort = Int
connect_to_next_port :: ValidPort -> ValidPort
connect_to_next_port = ...
But not just any Int
is a valid port; only integers between 1024 and 65535 (ports in TCP/IP are 16-bit values). So imagining we had a way to define a restricted type, we can eliminate the ValueError
with
type ValidPort = { x :: Int | 1024 <= x <= 65535 }
connect_to_next_port :: ValidPort -> ValidPort
connect_to_next_port = ...
But we might not find a port to return. Instead of raising an exception, we return something of type Maybe ValidPort
, which you can think of as a wrapper that includes both values of type ValidPort
and the value None
(which corresponds roughly to Nothing
in Haskell).
type ValidPort = { x :: Int | 1024 <= x <= 65535 }
connect_to_next_port :: ValidPort -> Maybe ValidPort
connect_to_next_port = ...
All of which to say is, we document exceptions that can be encoded into the return type, not ones that can be eliminated with an appropriate argument type.