所以我试图在这里采取正确的路径并了解如何解决这个警察,这在我看来只是一小段代码,为什么它会抱怨?
移动嵌套的 if-else 不会改变任何东西,关于如何解决这个问题有什么建议吗?
class WebPush::Register
include Interactor
# rubocop:disable Metrics/AbcSize
def call
user = Learner.find_by(id: context.user_id)
# return if existing
if user.web_push_subscription
context.subscription = user.web_push_subscription
else
subscription = WebPushSubscription.new(
endpoint: context.push_params[:endpoint],
auth_key: context.push_params[:keys][:auth],
p256dh_key: context.push_params[:keys][:p256dh],
learner: user
)
if subscription.save
context.subscription = subscription
else
context.error = subscription.errors.full_messages
context.fail!
end
end
end
# rubocop:enable Metrics/AbcSize
end
首先,你需要了解ABC是如何计算的。条件语句的嵌套不会影响 ABC。 RuboCop 的警告输出显示了计算结果:
Assignment Branch Condition size for call is too high. [<5, 28, 4> 28.72/17]
<5, 28, 4>
是您的 <Assignments, Branches, Conditionals>
,如本文中所述。
总分计算如下:
sqrt(5^2 + 28^2 + 4^2) = 28.72
警察的默认最高分数是 17。
我在下面注释了你的代码,并标注了每行的 ABC 分数。
def call
user = Learner.find_by(id: context.user_id) # <1, 3, 0>
if user.web_push_subscription # <0, 1, 1>
context.subscription = user.web_push_subscription # <1, 3, 0>
else # <0, 0, 1>
# this constructor call is the most expensive part
subscription = WebPushSubscription.new( # <1, 1, 0>
endpoint: context.push_params[:endpoint], # <0, 3, 0>
auth_key: context.push_params[:keys][:auth], # <0, 4, 0>
p256dh_key: context.push_params[:keys][:p256dh], # <0, 4, 0>
learner: user # <0, 0, 0>
)
if subscription.save # <0, 1, 1>
context.subscription = subscription # <1, 2, 0>
else # <0, 0, 1>
context.error = subscription.errors.full_messages # <1, 4, 0>
context.fail! # <0, 2, 0>
end
end
end
每次引用
context
时,指标都会添加一个 B 点。这是因为 context
不是 call
函数的局部变量,因此该度量假设每次都是新的方法调用。
如果您设置 cop 选项:CountRepeatedAttributes: false(我推荐),它只会为所有对
context
的引用添加一个 B 点组合。这使得 ABC 分数降至 19.1
。
取而代之的是,您可以通过将
WebPushSubscription
的创建提取到它自己的方法中来降低分数,如下所示:
def call
user = Learner.find_by(id: context.user_id)
if user.web_push_subscription
context.subscription = user.web_push_subscription
else
create_subscription(user)
end
end
private
def create_subscription(user)
push_params = context.push_params
subscription = WebPushSubscription.new(
endpoint: push_params[:endpoint],
auth_key: push_params.dig(:key, :auth),
p256dh_key: push_params.dig(:key, :p256dh),
learner: user
)
if subscription.save
context.subscription = subscription
else
context.error = subscription.errors.full_messages
context.fail!
end
end
这将在两种方法之间分配分数。请注意
create_subscription
中的一些额外的 ABC 保存策略,例如将 push_params
分配给变量,以及使用 dig
作为嵌套哈希访问器。 create_subscription
的最终分数在 12 到 16 之间,具体取决于您使用的警察选项,而 call
则在 6 到 8 之间。
一般来说,降低 ABC 分数所需的只是重构为更小的方法。
ABC 代表分配、分支、条件。
ABC = sqrt(A^2 + B^2 + C^2)
我们来数一下吧。
def call
user = Learner.find_by(id: context.user_id) // assignment, branches
# return if existing
if user.web_push_subscription // branch, condition
context.subscription = user.web_push_subscription // assignment, branches
else // condition
subscription = WebPushSubscription.new( // assignment, branch
endpoint: context.push_params[:endpoint], // branches
auth_key: context.push_params[:keys][:auth], // branches
p256dh_key: context.push_params[:keys][:p256dh], // branches
learner: user
)
if subscription.save // branch, condition
context.subscription = subscription // assignment
else // condition
context.error = subscription.errors.full_messages // assignment, branches
context.fail! // branch
end
end
end
这给出了 18.2。默认情况下,17 是 Rubcop 认为满意的最大值。
我们可以通过一些简单的提取方法来处理这个问题。
private def user
@user ||= Learner.find_by(id: context.user_id)
end
private def create_web_push_subscription_from_context
subscription = WebPushSubscription.new(
endpoint: context.push_params[:endpoint],
auth_key: context.push_params[:keys][:auth],
p256dh_key: context.push_params[:keys][:p256dh],
learner: user
)
if subscription.save
context.subscription = subscription
else
context.error = subscription.errors.full_messages
context.fail!
end
end
def call
if user.web_push_subscription
context.subscription = user.web_push_subscription
else
create_web_push_subscription_from_context
end
end
重构后代码更加简单,每个方法只做一件事。这就是 ABC 指标所鼓励的。