如何处理静态类型语言中的各种错误(或一般输入时)

How does one deal with various errors in statically typed languages (or when typing in general)

对于上下文,我的主要语言是 Python,我才刚刚开始使用注释。这是为学习 C++ 做准备(并且因为直觉上感觉更好)。


我有这样的东西:

from models import UserLocation
from typing import Optional
import cluster_module
import db
def get_user_location(user_id: int, data: list) -> Optional[UserLocation]:
    loc = UserLocation.query.filter_by(user_id=user_id).one_or_none()
    if loc:
        return loc
    try:
        clusters = cluster_module.cluster(data)
    except ValueError:
        return None # cluster throws an error if there is not enough data to cluster

    if list(clusters.keys()) == [-1]:
        return None # If there is enough data to cluster, the cluster with an index of -1 represents all data that didn't fit into a cluster. It's possible for NO data to fit into a cluster.
    loc = UserLocation(user_id=user_id, location = clusters[0].center)
    db.session.add(loc)
    db.session.commit()
    return loc

所以,我使用 typing.Optional 来确保我可以 return None 以防出现错误(如果我理解正确的话,静态类型语言等价于此是 return 适当类型的空指针)。但是,如何区分这两个错误呢?例如,我想做的是 return -1 如果没有足够的数据来聚类, -2 如果有数据,但是 none 它们适合集群(或类似的东西)。在 Python 中,这很容易(因为它不是静态类型的)。即使 mypy,我也可以说 typing.Union[UserLocation, int]

但是,如何在 C++ 或 Java 中做到这一点? Java 程序员是否需要做一些事情,比如将函数设置为 return int,并且 return UserLocation 的 ID 而不是对象本身(然后,任何使用 get_user_location 函数的代码本身都会进行查找)?这样做对运行时有好处吗,还是只是重构代码以适应语言是静态类型的事实?

我相信我了解静态类型的大部分明显好处 w.r.t。代码可读性、编译时间和运行时效率——但我不确定如何处理这个特定问题。

简而言之:如何处理表明它们 运行 在静态类型语言中出现不同错误的函数(return 非基本类型)?

与 python 解决方案等效的直接 C++ 是 std::variant<T, U>,其中 T 是预期的 return 值,U 是错误代码类型。然后您可以检查变体包含哪些类型并从那里开始。例如:

#include <cstdlib>
#include <iostream>
#include <string>
#include <variant>

using t_error_code = int;

// Might return either `std::string` OR `t_error_code`
std::variant<std::string, t_error_code> foo()
{
    // This would cause a `t_error_code` to be returned
    //return 10;

    // This causes an `std::string` to be returned
    return "Hello, World!";
}

int main()
{
    auto result = foo();

    // Similar to the Python `if isinstance(result, t_error_code)`
    if (std::holds_alternative<t_error_code>(result))
    {
        const auto error_code = std::get<t_error_code>(result);
        std::cout << "error " << error_code << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << std::get<std::string>(result) << std::endl;
}

然而,这在实践中并不常见。如果一个函数预计会失败,那么单个失败的 return 值(如 nullptrend 迭代器就足够了。这种失败是预料之中的,不是错误。如果失败是意外的,则首选异常,这也消除了您在此处描述的问题。既期望失败又关心失败原因的细节是不寻常的。