如何批量识别并修改代码库中的特定硬编码逻辑?
解决方案概览
针对15万份Python和Java代码文件的硬编码城市-国家映射迁移需求,核心分为三个步骤:静态代码分析识别目标模式、提取映射规则生成配置文件、批量替换原代码逻辑。以下是分语言的具体实现方案:
Python 代码处理方案
1. 识别硬编码模式
Python的目标模式包括直接函数调用比较、变量赋值后比较、三元表达式三种,使用AST(抽象语法树)分析能避免正则的误匹配问题。
示例代码(基于ast模块):
import ast def extract_city_mapping_python(code): tree = ast.parse(code) mappings = {} # 跟踪存储get_city()结果的变量 assigned_vars = {} for node in ast.walk(tree): # 记录变量赋值:如city = get_city() if isinstance(node, ast.Assign): for target in node.targets: if (isinstance(target, ast.Name) and isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Name) and node.value.func.id == 'get_city'): assigned_vars[target.id] = True # 处理If语句 elif isinstance(node, ast.If): if isinstance(node.test, ast.Compare): left = node.test.left city_val = None # 匹配get_city() == 'xxx' if (isinstance(left, ast.Call) and isinstance(left.func, ast.Name) and left.func.id == 'get_city' and isinstance(node.test.comparators[0], ast.Constant) and isinstance(node.test.comparators[0].value, str)): city_val = node.test.comparators[0].value # 匹配city == 'xxx'(city是get_city()的赋值变量) elif (isinstance(left, ast.Name) and left.id in assigned_vars and isinstance(node.test.comparators[0], ast.Constant) and isinstance(node.test.comparators[0].value, str)): city_val = node.test.comparators[0].value if city_val: # 提取then分支的country赋值 for body_node in node.body: if (isinstance(body_node, ast.Assign) and any(isinstance(t, ast.Name) and t.id == 'country' for t in body_node.targets) and isinstance(body_node.value, ast.Constant) and isinstance(body_node.value.value, str)): mappings[city_val.lower()] = body_node.value.value # 提取else分支的默认值 for orelse_node in node.orelse: if (isinstance(orelse_node, ast.Assign) and any(isinstance(t, ast.Name) and t.id == 'country' for t in orelse_node.targets) and isinstance(orelse_node.value, ast.Constant) and isinstance(orelse_node.value.value, str)): mappings['default'] = orelse_node.value.value # 处理三元表达式:country = 'India' if get_city() == 'Delhi' else 'US' elif isinstance(node, ast.IfExp): test = node.test left = test.left city_val = None if (isinstance(left, ast.Call) and isinstance(left.func, ast.Name) and left.func.id == 'get_city' and isinstance(test.comparators[0], ast.Constant) and isinstance(test.comparators[0].value, str)): city_val = test.comparators[0].value elif (isinstance(left, ast.Name) and left.id in assigned_vars and isinstance(test.comparators[0], ast.Constant) and isinstance(test.comparators[0].value, str)): city_val = test.comparators[0].value if city_val: if (isinstance(node.body, ast.Constant) and isinstance(node.body.value, str)): mappings[city_val.lower()] = node.body.value if (isinstance(node.orelse, ast.Constant) and isinstance(node.orelse.value, str)): mappings['default'] = node.orelse.value return mappings
2. 生成YAML配置文件
收集所有提取到的映射,合并去重后生成YAML:
import yaml def generate_yaml_config(mappings, output_path='city_mapping.yaml'): default_val = mappings.pop('default', None) with open(output_path, 'w') as f: yaml.dump(mappings, f, sort_keys=False) # 追加默认值配置 if default_val: with open(output_path, 'a') as f: f.write(f"'default': '{default_val}'\n")
3. 替换原代码逻辑
使用AST修改代码,将原有的if-else/三元表达式替换为配置读取逻辑:
import ast def replace_python_code(code): tree = ast.parse(code) # 标记需要替换的节点 nodes_to_replace = [] assigned_vars = {} # 先遍历标记目标节点(逻辑同提取映射时的识别逻辑) # ...(省略重复的识别逻辑,仅标记需要替换的If/IfExp节点) # 构造新的代码节点 new_code = """ data = yaml.safe_load(open('city_mapping.yaml')) country = data.get(get_city().lower(), data.get('default', 'US')) """ new_tree = ast.parse(new_code).body # 替换目标节点 for node in nodes_to_replace: # 找到节点的父节点,替换子节点 parent = None for n in ast.walk(tree): if node in getattr(n, 'body', []) or node in getattr(n, 'orelse', []) or node in getattr(n, 'values', []): parent = n break if parent: if isinstance(parent, ast.Module): parent.body = [new_tree[0], new_tree[1]] + [n for n in parent.body if n != node] else: # 针对函数内的节点替换 if node in parent.body: idx = parent.body.index(node) parent.body[idx:idx+1] = new_tree return ast.unparse(tree)
Java 代码处理方案
Java使用JavaParser库进行AST分析,处理逻辑与Python类似,但需适配Java语法特性。
1. 识别硬编码模式
示例代码:
import com.github.javaparser.JavaParser; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.stmt.*; import java.io.File; import java.util.HashMap; import java.util.Map; public class CityMappingExtractor { private static final Map<String, String> mappings = new HashMap<>(); private static final String GET_CITY_METHOD = "getCity"; public static Map<String, String> extractFromFile(File file) throws Exception { CompilationUnit cu = JavaParser.parse(file); mappings.clear(); Map<String, Boolean> assignedVars = new HashMap<>(); // 记录存储getCity()结果的变量 cu.findAll(AssignStmt.class).forEach(assign -> { if (assign.getTarget() instanceof NameExpr) { NameExpr target = (NameExpr) assign.getTarget(); if (assign.getValue() instanceof MethodCallExpr) { MethodCallExpr call = (MethodCallExpr) assign.getValue(); if (call.getName().asString().equals(GET_CITY_METHOD)) { assignedVars.put(target.getNameAsString(), true); } } } }); // 处理If语句 cu.findAll(IfStmt.class).forEach(ifStmt -> { Expression condition = ifStmt.getCondition(); String cityVal = null; if (condition instanceof BinaryExpr) { BinaryExpr binary = (BinaryExpr) condition; Expression left = binary.getLeft(); Expression right = binary.getRight(); // 匹配getCity().equals("xxx") if (left instanceof MethodCallExpr) { MethodCallExpr call = (MethodCallExpr) left; if (call.getName().asString().equals("equals") && call.getArguments().size() == 1 && call.getArguments().get(0) instanceof StringLiteralExpr && call.getScope().isPresent() && call.getScope().get() instanceof MethodCallExpr && ((MethodCallExpr) call.getScope().get()).getName().asString().equals(GET_CITY_METHOD)) { cityVal = ((StringLiteralExpr) call.getArguments().get(0)).getValue(); } } // 匹配city.equals("xxx")(city是getCity()的赋值变量) else if (left instanceof NameExpr && assignedVars.containsKey(((NameExpr) left).getNameAsString()) && right instanceof MethodCallExpr) { MethodCallExpr call = (MethodCallExpr) right; if (call.getName().asString().equals("equals") && call.getArguments().size() == 1 && call.getArguments().get(0) instanceof StringLiteralExpr) { cityVal = ((StringLiteralExpr) call.getArguments().get(0)).getValue(); } } } if (cityVal != null) { // 提取then分支的country赋值 ifStmt.getThenStmt().ifPresent(stmt -> { extractCountryAssignment(stmt, cityVal.toLowerCase()); }); // 提取else分支的默认值 ifStmt.getElseStmt().ifPresent(stmt -> { extractCountryAssignment(stmt, "default"); }); } }); // 处理三元表达式 cu.findAll(ConditionalExpr.class).forEach(condExpr -> { Expression condition = condExpr.getCondition(); String cityVal = null; if (condition instanceof BinaryExpr) { BinaryExpr binary = (BinaryExpr) condition; Expression left = binary.getLeft(); if (left instanceof MethodCallExpr) { MethodCallExpr call = (MethodCallExpr) left; if (call.getName().asString().equals("equals") && call.getArguments().size() == 1 && call.getArguments().get(0) instanceof StringLiteralExpr && call.getScope().isPresent() && call.getScope().get() instanceof MethodCallExpr && ((MethodCallExpr) call.getScope().get()).getName().asString().equals(GET_CITY_METHOD)) { cityVal = ((StringLiteralExpr) call.getArguments().get(0)).getValue(); } } } if (cityVal != null) { if (condExpr.getThenExpr() instanceof StringLiteralExpr) { mappings.put(cityVal.toLowerCase(), ((StringLiteralExpr) condExpr.getThenExpr()).getValue()); } if (condExpr.getElseExpr() instanceof StringLiteralExpr) { mappings.put("default", ((StringLiteralExpr) condExpr.getElseExpr()).getValue()); } } }); return new HashMap<>(mappings); } private static void extractCountryAssignment(Stmt stmt, String key) { if (stmt instanceof ExpressionStmt) { Expression expr = ((ExpressionStmt) stmt).getExpression(); if (expr instanceof AssignExpr) { AssignExpr assign = (AssignExpr) expr; if (assign.getTarget() instanceof NameExpr && ((NameExpr) assign.getTarget()).getNameAsString().equals("country") && assign.getValue() instanceof StringLiteralExpr) { mappings.put(key, ((StringLiteralExpr) assign.getValue()).getValue()); } } } } }
2. 生成YAML配置
使用SnakeYAML库生成配置:
import org.yaml.snakeyaml.Yaml; import java.io.FileWriter; import java.io.IOException; import java.util.Map; public class YamlGenerator { public static void generate(Map<String, String> mappings, String outputPath) throws IOException { Yaml yaml = new Yaml(); try (FileWriter writer = new FileWriter(outputPath)) { yaml.dump(mappings, writer); } } }
3. 替换Java代码逻辑
通过JavaParser修改AST,替换原逻辑为配置读取代码:
import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.stmt.BlockStmt; public class JavaCodeReplacer { public static void replaceCode(CompilationUnit cu) { // 导入YAML相关类 cu.addImport("org.yaml.snakeyaml.Yaml"); cu.addImport("java.util.Map"); // 遍历所有方法,替换目标逻辑 cu.findAll(MethodDeclaration.class).forEach(method -> { BlockStmt body = method.getBody().orElse(null); if (body == null) return; // 标记需要替换的IfStmt/ConditionalExpr节点(逻辑同提取映射时的识别) // ... // 构造新的代码语句 String newCode = """ Map<String, String> cityMapping = new Yaml().load(YourClass.class.getResourceAsStream("/city_mapping.yaml")); String country = cityMapping.getOrDefault(getCity().toLowerCase(), cityMapping.get("default")); """; body.getStatements().add(JavaParser.parseStatement(newCode)); // 删除原有的目标节点 // ... }); } }
大规模处理注意事项
- 性能优化:使用多线程/进程遍历文件系统,避免单线程处理15万文件的卡顿。
- 误匹配规避:严格限定目标函数名(
get_city/getCity)和变量名(country),统一字符串大小写处理。 - 备份与验证:批量修改前备份所有文件,人工抽查映射结果,修改后运行单元测试验证逻辑正确性。
- 复杂场景兼容:处理多elif分支、嵌套if、不同字符串引号格式等场景。
内容的提问来源于stack exchange,提问作者Devavrata
相关产品推荐
相关产品推荐

