dart null 安全的最佳实践
我目前正在尝试提高我的 flutter 应用程序中的 null 安全性,但是在处理 null 安全性方面的实际经验相对较少,我对自己的一些决定没有信心。
例如,我的应用程序需要用户登录,因此我有一个 Auth 类来保留身份验证的状态。
class Auth {
final String? token;
final User? user;
Auth(this.token, this.user);
}
在我的应用程序中,我确保仅当用户登录时才访问 user
属性,因此执行以下操作是安全的:
final auth = Auth(some_token, some_user);
// and when I need to access the user
final user = auth.user!
这导致了第一个问题:
是否建议使用应用程序中的许多地方都存在空断言运算符?
我个人觉得在整个应用程序中执行诸如 auth.user!.id
之类的事情有点不舒服,所以我目前正在像这样处理它this:
class Auth {
final User? _user;
Auth(this._token, this._user);
User get user {
if (_user == null) {
throw StateError('Invalid user access');
} else {
return _user!;
}
}
}
但我不确定这是否是推荐的做法空安全。
对于下一个问题,我有一个处理 API 调用的类:
class ApiCaller {
final String token;
ApiCaller(this.token);
Future<Data> getDataFromBackend() async {
// some code that requires the token
}
}
// and is accessed through riverpod provider
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
return ApiCaller(auth.token);
})
我的 ApiCaller 是通过提供程序访问的,因此该对象是在应用程序启动时创建的。显然,它需要 token
可用,因此依赖于 Auth
。但是,当应用启动且用户未登录时,token
也可能为 null
。
因为我确信 apiCaller
不是当没有现有用户时使用,这样做:
class ApiCaller {
// make token nullable
final String? token;
ApiCaller(this.token);
Future<Data> getDataFromBackend() async {
// some code that requires the token
// and use token! in all methods that need it
}
}
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
if (auth.token == null) {
return ApiCaller()
} else {
return ApiCaller(auth.token);
}
})
应该没问题。然而,这也让我在所有方法中使用了大量的 token!
,而且我对此不太确定。
我也可以简单地在非空 token
版本中执行 ApiCaller('')
操作,但这似乎更像是一种解决方法,而不是一个好的实践。
抱歉问了这么长的问题。我尝试寻找一些关于零安全的实际实践的更好的文章,但大多数只是语言基础知识,所以我希望这里的一些人能给我一些见解。提前致谢!
I'm currently trying to improve null safety in my flutter app, but having relatively less real-world experiences in working with null safety, I'm not confident in some of my decisions.
For example, my app requires user login, and so I have an Auth
class that preserves the state of authentication.
class Auth {
final String? token;
final User? user;
Auth(this.token, this.user);
}
In my app, I ensure that the user
property is accessed only when the user is logged in, so it's safe do the following:
final auth = Auth(some_token, some_user);
// and when I need to access the user
final user = auth.user!
which leads to the first question:
Is it recommended to use the null assertion operator in many places in the app?
I personally find it kind of uncomfortable to do things like auth.user!.id
throughout the app, so I'm currently handling it like this:
class Auth {
final User? _user;
Auth(this._token, this._user);
User get user {
if (_user == null) {
throw StateError('Invalid user access');
} else {
return _user!;
}
}
}
but I'm not sure if this is a recommended practice in null-safety.
For the next question, I have a class that handles API calls:
class ApiCaller {
final String token;
ApiCaller(this.token);
Future<Data> getDataFromBackend() async {
// some code that requires the token
}
}
// and is accessed through riverpod provider
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
return ApiCaller(auth.token);
})
My ApiCaller
is accessed through providers and thus the object is created when the App starts. Obviously, it requires token
to be available and thus depends on Auth
. However, token
could also be null
when the app starts and the user is not logged in.
Since I'm confident that apiCaller
isn't used when there is no existing user, doing this:
class ApiCaller {
// make token nullable
final String? token;
ApiCaller(this.token);
Future<Data> getDataFromBackend() async {
// some code that requires the token
// and use token! in all methods that need it
}
}
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
if (auth.token == null) {
return ApiCaller()
} else {
return ApiCaller(auth.token);
}
})
should be fine. However, this also makes me use a lot of token!
throughout all methods, and I'm not too sure about that.
I could also simply do ApiCaller('')
in the non-null token
version, but this seems more of a workaround than a good practice.
Sorry for the lengthy questions. I tried looking for some better articles about real-world practices in null-safety but most are only language basics, so I hope some of you on here could give me some insights. Thanks in advance!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
当您知道可空变量不为空时,避免使用
!
的最简单方法是创建一个非空 getter,就像您在第一个问题中所做的那样:我会让您知道没有必要在抛出错误之前检查该值是否为 null,null 检查运算符正是这样做的:
当然,除非您非常关心错误本身并且想要抛出不同的错误。
至于你的第二个问题,这个问题有点棘手,你知道在初始化之前你不会不会访问变量,但你必须在它有值之前初始化它,因此你唯一的选择是要使其为空,我个人不喜欢使用
late
关键字,但它是专门为此目的而构建的,因此您可以使用它。延迟变量在明确分配之前不会有值,否则会抛出错误,另一种解决方案是像在其他页面上那样创建一个非空 getter。另外,你不需要在这里进行空检查,因为结果是相同的:
而是这样做:
这对我来说确实感觉像一个简单的问题,你只是不习惯使用空安全,这意味着对你来说,
!
看起来丑陋或不安全,但是您使用它的次数越多,您就会对它越熟悉,即使您在应用程序中经常使用它,它也不会看起来像糟糕的代码。希望我的回答对你有帮助
The simplest way to avoid using
!
when you know a nullable variable is not null is by making a non-null getter like you did on your first question:I will let you know that there is no need to check if the value is null before throwing an error, the null check operator does exactly that:
Unless of course you care a lot about the error itself and want to throw a different error.
As for your second question, that one is a bit trickier, you know you will not access the variable before it is initialized, but you have to initialize it before it has a value, thus your only option is to make it null, I personally don't like to use the
late
keyword, but it was built expressly for this purpose, so you could use it. A late variable will not have a value until expressly assigned, and it will throw an error otherwise, another solution is to make a non-null getter like on the other page.Also, you don't need a null check here because the result is the same:
instead do this:
This does feel to me like a simple problem, you are just not used to working with null-safety, which means that to you, the
!
looks ugly or unsafe, but the more you work with it the more you'll become comfortable with it and the less it will look as bad code even if you use it a lot around your app.Hopefylly, my answer is helpful to you
我认为空断言运算符有点代码味道,并尽可能避免使用它。在许多情况下,可以通过使用局部变量、检查
null
并允许发生类型提升 或使用可识别 null 的运算符来避免这种情况失败。在某些情况下,只要您能够从逻辑上保证该值不会为
null
,使用 null 断言就会更简单、更清晰。如果您可以接受应用程序因失败的空断言而崩溃(因为这在逻辑上是不可能的),那么使用它就完全没问题。除非您想控制错误,否则抛出 StateError 是没有意义的。无论如何,空断言运算符都会抛出错误(
TypeError
)。我个人认为
user
getter 没有多大价值。您仍然会在任何地方使用空断言运算符,但它只是隐藏在方法调用后面。它会使代码更漂亮,但不太清楚潜在的故障点在哪里。如果您发现自己在一个函数中多次对同一变量使用空断言运算符,您仍然可以使用局部变量来使其更好:
我认为最终您需要决定您想要的公共 API 以及它的契约是。例如,如果用户未登录,那么拥有
Auth
对象是否有意义?您能否让Auth
使用不可为 null 的成员,并让消费者使用Auth?
而不是Auth
wherenull
表示“未登录”?虽然这会将责任转嫁给调用者,让他们到处检查null
,但他们已经有责任在未登录时不执行任何访问Auth.user
的操作 另一个 API 考虑因素是您希望的故障模式是什么。您的 API 合同是否在明确的文档中规定调用者在未登录时不得访问
Auth.user
?如果来电者有疑问,他们可以自行检查吗?如果是这样,那么当Auth.user
为null
时,对Auth.user
的访问是致命的,这是合理的:调用者由于应该纠正的逻辑错误而违反了合同。然而,在某些情况下,这可能过于严厉。无论如何,您的操作可能会因其他原因在运行时失败。在这些情况下,您可以考虑优雅地失败,例如向调用者返回
null
或一些错误代码。您提供的
ApiCaller
类没有零参数构造函数,因此这是没有意义的。如果你的意思是它的构造函数是:那么
ApiCaller()
和ApiCaller(null)
之间没有区别,所以你不妨无条件地使用ApiCaller( auth.token)
。I consider the null assertion operator to be a bit of a code smell and try avoid using it if possible. In many cases, it can be avoided by using a local variable, checking for
null
, and allowing type promotion to occur or by using null-aware operators for graceful failure.In some cases, it's simpler and cleaner to use the null assertion as long as you can logically guarantee that the value will not be
null
. If you're okay with your application crashing from a failed null assertion because that should be logically impossible, then using it is perfectly fine.Unless you want to control the error, throwing the
StateError
is pointless. The null assertion operator will throw an error (aTypeError
) anyway.I personally don't see much value in the
user
getter. You'd still be using the null assertion operator everywhere, but it'd just be hidden behind a method call. It'd make the code prettier, but it'd be less clear where potential failure points are.If you find yourself using the null assertion operator for the same variable multiple times in a function, you still can use a local variable to make that nicer:
I think ultimately you need to decide what you want your public API to be and what its contracts are. For example, if a user is not logged in, does it make sense to have an
Auth
object at all? Could you instead have makeAuth
use non-nullable members, and have consumers useAuth?
instead ofAuth
wherenull
means "not logged in"? While that would be passing the buck to the callers, making them check fornull
everywhere instead, they're already responsible to not do anything that accessesAuth.user
when not logged in.Another API consideration is what you want the failure mode to be. Does your API contract stipulate in clear documentation that callers must never access
Auth.user
when not logged in? If the caller is in doubt, are they able to check themselves? If so, then making accesses toAuth.user
fatal when it'snull
is reasonable: the caller violated the contract due to a logical error that should be corrected.However, in some situations maybe that's too harsh. Maybe your operation can fail at runtime for other reasons anyway. In those cases, you could consider failing gracefully, such as by returning
null
or some error code to the caller.Your
ApiCaller
class as presented does not have a zero-argument constructor, so that doesn't make sense. If you meant for its constructor to be:then there's no difference between
ApiCaller()
andApiCaller(null)
, so you might as well just unconditionally useApiCaller(auth.token)
.