# Shotgun parsing
Shotgun parsing is a programming antipattern whereby parsing and input-validating code is **mixed with** and **spread across** processing code - throwing a cloud of checks at the input, and hoping, without any systematic justification, that one or another would catch all the “bad” cases.[^1]
Here, `x` and `y` is not properly validated at [[Software Edge|edges]].
```python
def rectangle_area(x: float, y: float) -> float:
if x <= 0:
raise ValueError("x should be positive")
if y <= 0:
raise ValueError("y should be positive")
return x * y
def rectangle_perimeter(x: float, y: float) -> float:
if x <= 0:
raise ValueError("x should be positive")
if y <= 0:
raise ValueError("y should be positive")
return (x + y) * 2
def run() -> tuple[float, float]:
x = float(input("x? "))
y = float(input("y? "))
return rectangle_area(x, y), rectangle_perimeter(x, y)
```
## Assert valid space
Instead of handling inputs that fit with the function signature but that are edge cases that doesn't fit with the valid space of the [[function]], one valid approach would be to use the EAFP principle: let it happens and hope that everything works.
```python
def rectangle_area(x: float, y: float) -> float:
assert x > 0
assert y > 0
return x * y
def rectangle_perimeter(x: float, y: float) -> float:
assert x > 0
assert y > 0
return (x + y) * 2
```
By using asserts [[Statement|statements]] that would be removed when the `-O` optimization option is enabled, the expected correct data is assert during test phase while reducing the runtime cost to zero in production. Those assert doesn't not validate input data. Instead, it is an opportunity to detect that the input data doesn't fit expectation and should be validated prior to this [[function]] [[Call|calls]].
## Explicit valid space in annotations metadata
One way to better inform users of the [[function]] would be to use the `annotated-types` library:[^2]
```python
def rectangle_area(
x: typing.Annotated[float, annotated_types.Gt(0)],
y: typing.Annotated[float, annotated_types.Gt(0)],
) -> float:
assert x > 0
assert y > 0
return x * y
def rectangle_perimeter(
x: typing.Annotated[float, annotated_types.Gt(0)],
y: typing.Annotated[float, annotated_types.Gt(0)],
) -> float:
assert x > 0
assert y > 0
return (x + y) * 2
```
Those [[Type Annotation|annotations]] are metadata ignore by [[TypeChecker|typecheckers]] [^3] but can be used by other tools i.e. generated documentation making explicit that inputs needs to be greater than 0 floats.
When running tests, if there is [[Negative Testing|negative tests]] in place, failed or missing validation might show up in results thanks to those asserts.
## Explicit valid space in type
Using an explicit type like `NonZero` and a typeguard,[^4] [[TypeChecker|typecheckers]] will be able to warn users that they did not properly validated [[Argument|arguments]] before sending it to the [[function]].
```python
def rectangle_area(
x: NonZero,
y: NonZero,
) -> NonZero:
assert x > 0
assert y > 0
res = x * y # res is an int
assert is_non_zero(res)
return res # res is now NonZero
def rectangle_perimeter(
x: NonZero,
y: NonZero,
) -> NonZero:
assert x > 0
assert y > 0
res = (x + y) * 2 # res is an int
assert is_non_zero(res)
return res # res is now NonZero
def run_a() -> tuple[float, float]:
x = float(input("x? "))
y = float(input("y? "))
area = rectangle_area(x, y) # Typechecker error
perimeter = rectangle_perimeter(x, y) # Typechecker error
return area, perimeter
def run_b() -> tuple[NonZero, NonZero]:
def non_negative_adapter(x: float) -> NonZero:
if x == 0:
# handle zero value i.e. log error
assert is_non_zero(x)
return x
x = non_negative_adapter(float(input("x? ")))
y = non_negative_adapter(float(input("y? ")))
return rectangle_area(x, y), rectangle_perimeter(x, y)
```
[^1]: [The Seven Turrets of Babel - langsec.org](https://langsec.org/papers/langsec-cwes-secdev2016.pdf)
[^2]: [annotated-types - github.com](https://github.com/annotated-types/annotated-types)
[^3]: [[TypeChecker#Annotation metadata]]
[^4]: [typing.Typeguard - docs.python.org](https://docs.python.org/3/library/typing.html#typing.TypeGuard)