Delta analysis

Reviewed on Jul 16, 14:07

Review level:
View on GitHub
Code Health
5.96
(+0.38)
Code Health Quality gate
Passed
Declining Code Health
0 findings
Improving Code Health
10 findings
Affected Hotspots
1 File
Authors

TM

CrosswalkModule::applySlowDownByOcclusion has a cyclomatic complexity of 12, threshold = 9

A Complex Method has a high cyclomatic complexity. The recommended threshold for the C++ language is a cyclomatic complexity lower than 9.

There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using EXTRACT FUNCTION. Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the DECOMPOSE CONDITIONAL refactoring.

This code health issue has been solved before in this project. Here are some examples for inspiration:

scene_module_interface.cpp (7b7db7a4)
(+0.31)
...
Author: Mamoru Sobue <hilo.soblin@gmail.com>
Date: 2024-12-02 10:03+00:00
feat(behavior_velocity_planner)!: remove stop_reason (#9452)
Signed-off-by: Mamoru Sobue <mamoru.sobue@tier4.jp>
@@ -69,4 +69,2 @@ SceneModuleManagerInterface::SceneModuleManagerInterface(
std::string("/planning/velocity_factors/") + module_name, 1);
pub_stop_reason_ =
node.create_publisher<tier4_planning_msgs::msg::StopReasonArray>("~/output/stop_reasons", 1);
pub_infrastructure_commands_ =
@@ -109,6 +107,3 @@ void SceneModuleManagerInterface::modifyPathVelocity(
visualization_msgs::msg::MarkerArray debug_marker_array;
tier4_planning_msgs::msg::StopReasonArray stop_reason_array;
autoware_adapi_v1_msgs::msg::VelocityFactorArray velocity_factor_array;
stop_reason_array.header.frame_id = "map";
stop_reason_array.header.stamp = clock_->now();
velocity_factor_array.header.frame_id = "map";
@@ -119,8 +114,6 @@ void SceneModuleManagerInterface::modifyPathVelocity(
first_stop_path_point_distance_ = autoware::motion_utils::calcArcLength(path->points);
for (const auto & scene_module : scene_modules_) {
tier4_planning_msgs::msg::StopReason stop_reason;
scene_module->resetVelocityFactor();
scene_module->setPlannerData(planner_data_);
scene_module->modifyPathVelocity(path, &stop_reason);
scene_module->modifyPathVelocity(path);
@@ -131,5 +124,2 @@ void SceneModuleManagerInterface::modifyPathVelocity(
}
if (stop_reason.reason != "") {
stop_reason_array.stop_reasons.emplace_back(stop_reason);
}
@@ -139,6 +129,2 @@ void SceneModuleManagerInterface::modifyPathVelocity(
if (scene_module->getFirstStopPathPointDistance() < first_stop_path_point_distance_) {
first_stop_path_point_distance_ = scene_module->getFirstStopPathPointDistance();
}
for (const auto & marker : scene_module->createDebugMarkerArray().markers) {
@@ -150,5 +136,2 @@ void SceneModuleManagerInterface::modifyPathVelocity(
if (!stop_reason_array.stop_reasons.empty()) {
pub_stop_reason_->publish(stop_reason_array);
}
pub_velocity_factor_->publish(velocity_factor_array);

CrosswalkModule::applySlowDown has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function

A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.

A bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is levels of conditionals.

Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the Bumpy Road code health issue.

A Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The EXTRACT FUNCTION refactoring is the primary response.

CrosswalkModule::applySlowDownByOcclusion has a nested complexity depth of 4, threshold = 4

Deep nested logic means that you have control structures like if-statements or loops inside other control structures. Deep nested logic increases the cognitive load on the programmer reading the code. The human working memory has a maximum capacity of 3-4 items; beyond that threshold, we struggle with keeping things in our head. Consequently, deep nested logic has a strong correlation to defects and accounts for roughly 20% of all programming mistakes.

CodeScene measures the maximum nesting depth inside each function. The deeper the nesting, the lower the code health. The threshold for the C++ language is 4 levels of nesting.

Occassionally, it's possible to get rid of the nested logic by Replacing Conditionals with Guard Clauses.

Another viable strategy is to identify smaller building blocks inside the nested chunks of logic and extract those responsibilities into smaller, cohesive, and well-named functions. The EXTRACT FUNCTION refactoring explains the steps.

This code health issue has been solved before in this project. Here are some examples for inspiration:

non_maximum_suppression.cpp (01944efd)
(+0.45)
...
Author: Taekjin LEE <taekjin.lee@tier4.jp>
Date: 2024-12-11 01:05+00:00
fix(lidar_centerpoint): non-maximum suppression target decision logic (#9595)
* refactor(lidar_centerpoint): optimize non-maximum suppression search distance calculation Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> * feat(lidar_centerpoint): do not suppress if one side of the object is pedestrian Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> * style(pre-commit): autofix * refactor(lidar_centerpoint): remove unused variables Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> * refactor: remove unused variables Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> fix: implement non-maximum suppression logic to the transfusion Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> refactor: remove unused parameter iou_nms_target_class_names Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> Revert "fix: implement non-maximum suppression logic to the transfusion" This reverts commit b8017fc366ec7d67234445ef5869f8beca9b6f45. fix: revert transfusion modification --------- Signed-off-by: Taekjin LEE <taekjin.lee@tier4.jp> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@@ -24,2 +24,3 @@ namespace autoware::lidar_centerpoint
{
using Label = autoware_perception_msgs::msg::ObjectClassification;
@@ -31,11 +32,3 @@ void NonMaximumSuppression::setParameters(const NMSParams & params)
params_ = params;
target_class_mask_ = classNamesToBooleanMask(params.target_class_names_);
}
bool NonMaximumSuppression::isTargetLabel(const uint8_t label)
{
if (label >= target_class_mask_.size()) {
return false;
}
return target_class_mask_.at(label);
search_distance_2d_sq_ = params.search_distance_2d_ * params.search_distance_2d_;
}
@@ -50,7 +43,7 @@ bool NonMaximumSuppression::isTargetPairObject(
if (isTargetLabel(label1) && isTargetLabel(label2)) {
return true;
// if labels are not the same, and one of them is pedestrian, do not suppress
if (label1 != label2 && (label1 == Label::PEDESTRIAN || label2 == Label::PEDESTRIAN)) {
return false;
}
const auto search_sqr_dist_2d = params_.search_distance_2d_ * params_.search_distance_2d_;
const auto sqr_dist_2d = autoware::universe_utils::calcSquaredDistance2d(
@@ -58,3 +51,3 @@ bool NonMaximumSuppression::isTargetPairObject(
autoware::object_recognition_utils::getPose(object2));
return sqr_dist_2d <= search_sqr_dist_2d;
return sqr_dist_2d <= search_distance_2d_sq_;
}
@@ -75,10 +68,8 @@ Eigen::MatrixXd NonMaximumSuppression::generateIoUMatrix(
if (params_.nms_type_ == NMS_TYPE::IoU_BEV) {
const double iou = autoware::object_recognition_utils::get2dIoU(target_obj, source_obj);
triangular_matrix(target_i, source_i) = iou;
// NOTE: If the target object has any objects with iou > iou_threshold, it
// will be suppressed regardless of later results.
if (iou > params_.iou_threshold_) {
break;
}
const double iou = autoware::object_recognition_utils::get2dIoU(target_obj, source_obj);
triangular_matrix(target_i, source_i) = iou;
// NOTE: If the target object has any objects with iou > iou_threshold, it
// will be suppressed regardless of later results.
if (iou > params_.iou_threshold_) {
break;
}
@@ -99,6 +90,5 @@ std::vector<DetectedObject> NonMaximumSuppression::apply(
const auto value = iou_matrix.row(i).maxCoeff();
if (params_.nms_type_ == NMS_TYPE::IoU_BEV) {
if (value <= params_.iou_threshold_) {
output_objects.emplace_back(input_objects.at(i));
}
if (value <= params_.iou_threshold_) {
output_objects.emplace_back(input_objects.at(i));
}

CrosswalkModule::checkStopForStuckVehicles has 5 arguments, threshold = 4

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.

The threshold for the C++ language is 4 function arguments.

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

This code health issue has been solved before in this project. Here are some examples for inspiration:

shift_pull_over.cpp (13e6530d)
(+0.27)
...
Author: Mamoru Sobue <hilo.soblin@gmail.com>
Date: 2024-10-07 02:11+00:00
refactor(goal planner): hold modified_goal in PullOverPath ,copy modified goal once from background thread (#9006)
refactor(goal_planner): save modified_goal_pose in PullOverPlannerBase Signed-off-by: Mamoru Sobue <mamoru.sobue@tier4.jp>
@@ -37,4 +37,5 @@ ShiftPullOver::ShiftPullOver(
std::optional<PullOverPath> ShiftPullOver::plan(
const size_t goal_id, const size_t id, const std::shared_ptr<const PlannerData> planner_data,
const BehaviorModuleOutput & previous_module_output, const Pose & goal_pose)
const GoalCandidate & modified_goal_pose, const size_t id,
const std::shared_ptr<const PlannerData> planner_data,
const BehaviorModuleOutput & previous_module_output)
{
@@ -61,3 +62,3 @@ std::optional<PullOverPath> ShiftPullOver::plan(
const auto pull_over_path = generatePullOverPath(
goal_id, id, planner_data, previous_module_output, road_lanes, pull_over_lanes, goal_pose,
modified_goal_pose, id, planner_data, previous_module_output, road_lanes, pull_over_lanes,
lateral_jerk);
@@ -129,6 +130,6 @@ std::optional<PathWithLaneId> ShiftPullOver::cropPrevModulePath(
std::optional<PullOverPath> ShiftPullOver::generatePullOverPath(
const size_t goal_id, const size_t id, const std::shared_ptr<const PlannerData> planner_data,
const GoalCandidate & goal_candidate, const size_t id,
const std::shared_ptr<const PlannerData> planner_data,
const BehaviorModuleOutput & previous_module_output, const lanelet::ConstLanelets & road_lanes,
const lanelet::ConstLanelets & shoulder_lanes, const Pose & goal_pose,
const double lateral_jerk) const
const lanelet::ConstLanelets & shoulder_lanes, const double lateral_jerk) const
{
@@ -137,2 +138,4 @@ std::optional<PullOverPath> ShiftPullOver::generatePullOverPath(
const auto & goal_pose = goal_candidate.goal_pose;
// shift end pose is longitudinal offset from goal pose to improve parking angle accuracy
@@ -258,4 +261,4 @@ std::optional<PullOverPath> ShiftPullOver::generatePullOverPath(
auto pull_over_path_opt = PullOverPath::create(
getPlannerType(), goal_id, id, {shifted_path.path}, path_shifter.getShiftLines().front().start,
path_shifter.getShiftLines().front().end, {std::make_pair(pull_over_velocity, 0)});
getPlannerType(), id, {shifted_path.path}, path_shifter.getShiftLines().front().start,
goal_candidate, {std::make_pair(pull_over_velocity, 0)});

CrosswalkModule::checkStopForCrosswalkUsers decreases in cyclomatic complexity from 21 to 20, threshold = 9

CrosswalkModule::checkStopForStuckVehicles decreases in cyclomatic complexity from 19 to 18, threshold = 9

CrosswalkModule::modifyPathVelocity is no longer above the threshold for cyclomatic complexity

CrosswalkModule::applySafetySlowDownSpeed is no longer above the threshold for cyclomatic complexity

CrosswalkModule::modifyPathVelocity no longer has a complex conditional

CrosswalkModule::checkStopForCrosswalkUsers decreases from 5 to 4 logical blocks with deeply nested code, threshold is one single block per function

CrosswalkModule::applySafetySlowDownSpeed is no longer above the threshold for logical blocks with deeply nested code

The mean cyclomatic complexity decreases from 6.39 to 5.91, threshold = 4

CrosswalkModule::modifyPathVelocity is no longer above the threshold for nested complexity depth

WalkwayModule::modifyPathVelocity decreases in cyclomatic complexity from 12 to 11, threshold = 9