Delta analysis
Reviewed on Jul 16, 14:07
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:
@@ -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:
@@ -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:
@@ -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