关于代码审查的一些思考

news2024/11/17 20:27:05

作为一名代码审查员,首先我们已经具备了丰富的代码开发经验,并且对提交的代码工程非常熟悉

代码审查可以发现并纠正代码中的错误、缺陷和不良实践。通过多人对代码进行仔细的检查和讨论,能够发现一些单独开发时难以察觉的问题,从而提高代码的稳定性和可靠性。代码审查是一个持续的过程,通过有效的代码审查,可以提高代码质量、减少错误、促进团队协作和知识共享。同时通过审查他人的代码,我们可以学习新的技术、方法和最佳实践,同时加强彼此之间的沟通和理解。

综述

在审查代码时,我们通常的评审维度主要包括如下几个方面:

1、代码功能是否完善

2、代码结构是否合理,是否符合规范的编码方式,例如国内的话是否符合阿里java开发手册中的代码规范

3、代码是否充分复用,是否具备良好的扩展性

4、代码的命名是否规范,方法命名、变量命名是否体现了其对应的功能含义

5、在性能上是否能够继续优化,如果是O(n^2)的复杂度是否能优化成O(n)的复杂度

6、在内存占用上是否还能进一步压缩

7、在时间和空间二者之间的利弊权衡

8、复杂代码逻辑是否有对应的注释,注释是否清楚

9、代码是否存在安全性问题

--------------------------------------------------------长文预警----------------------------------------------------------

一、先来两个案例看下

案例1:我们看下下面代码如何优化

PowerMockBuilder

public class PowerMockBuilder extends MockitoMockBuilder{

    /**
     * true - field can be mocked
     */
    @Override
    public boolean isMockable(Field field, Type testedClass) {
        boolean openUserCheckDialog = fileTemplateCustomization.isOpenUserCheckDialog();
        boolean isMockable;
        if (openUserCheckDialog) {
            isMockable =  fileTemplateCustomization.getSelectedFieldNameList().contains(field.getName());
        } else {
            isMockable =  isMockableCommonChecks(field, testedClass);
        }
        LOG.debug("field " + field.getType().getCanonicalName() + " " + field.getName() + " is mockable:" + isMockable);
        return isMockable;
    }

}
MockitoMockBuilder
public class  MockitoMockBuilder implements MockBuilder{
    
    /**
     * true - field can be mocked
     */
    @SuppressWarnings("unused")
    @Override
    public boolean isMockable(Field field, Type testedClass) {
        boolean openUserCheckDialog = fileTemplateCustomization.isOpenUserCheckDialog();
        boolean isMockable;
        if (openUserCheckDialog) {
            isMockable = fileTemplateCustomization.getSelectedFieldNameList().contains(field.getName());
        } else {
            isMockable = isMockableCommonChecks(field, testedClass) && (!field.getType().isFinal() || isMockitoMockMakerInlineOn);
        }
        LOG.debug(
            "field " + field.getType().getCanonicalName() + " " + field.getName() + " is mockable:" + isMockable);
        return isMockable;
    }

    /**
     * checks if field in testedClass can be mocked. evaluates conditions common to all currently supported mock frameworks
     * @return true if input field in testedClass can be mocked
     */
    protected boolean isMockableCommonChecks(Field field, Type testedClass) {
        return !field.getType().isPrimitive() && !isWrapperType(field.getType())
                && !field.isOverridden() && !field.getType().isArray() && !field.getType().isEnum()
                && !testSubjectInspector.isNotInjectedInDiClass(field, testedClass) && !isInitInline(field);
    }
}

MockitoMockBuilder

public class  MockitoMockBuilder implements MockBuilder{
    
    /**
     * true - field can be mocked
     */
    @SuppressWarnings("unused")
    @Override
    public boolean isMockable(Field field, Type testedClass) {
        boolean openUserCheckDialog = fileTemplateCustomization.isOpenUserCheckDialog();
        boolean isMockable;
        if (openUserCheckDialog) {
            isMockable = fileTemplateCustomization.getSelectedFieldNameList().contains(field.getName());
        } else {
            isMockable = isMockableCommonChecks(field, testedClass) && (!field.getType().isFinal() || isMockitoMockMakerInlineOn);
        }
        LOG.debug(
            "field " + field.getType().getCanonicalName() + " " + field.getName() + " is mockable:" + isMockable);
        return isMockable;
    }

    /**
     * checks if field in testedClass can be mocked. evaluates conditions common to all currently supported mock frameworks
     * @return true if input field in testedClass can be mocked
     */
    protected boolean isMockableCommonChecks(Field field, Type testedClass) {
        return !field.getType().isPrimitive() && !isWrapperType(field.getType())
                && !field.isOverridden() && !field.getType().isArray() && !field.getType().isEnum()
                && !testSubjectInspector.isNotInjectedInDiClass(field, testedClass) && !isInitInline(field);
    }
}

我们从代码的结构能看出来,PowerMockBuilder继承了MockitoMockBuilder,且isMockable方法很大地方的逻辑是相似的,因此可以从复用的角度触发对其进行优化

优化后

PowerMockBuilder

public class PowerMockBuilder extends MockitoMockBuilder{
    /**
     * true - field can be mocked
     */
    protected boolean isMockableByMockFramework(Field field) {
        return true;
    }
}

MockitoMockBuilder

public class  MockitoMockBuilder implements MockBuilder{
    
    @Override
    public boolean isMockable(Field field, Type testedClass) {
        boolean openUserCheckDialog = fileTemplateCustomization.isOpenUserCheckDialog();
        boolean isMockable;
        if (openUserCheckDialog) {
            isMockable = fileTemplateCustomization.getSelectedFieldNameList().contains(field.getName());
        } else {
            isMockable = isMockableCommonChecks(field, testedClass) && isMockableByMockFramework(field);
        }
        LOG.debug(
            "field " + field.getType().getCanonicalName() + " " + field.getName() + " is mockable:" + isMockable);
        return isMockable;
    }
    
    /**
     *
     * @param field field to mock
     * @return true if field can be mocked in mock framework
     */
    protected boolean isMockableByMockFramework(Field field) {
        return !field.getType().isFinal() || isMockitoMockMakerInlineOn;
    }
    
    /**
     * checks if field in testedClass can be mocked. evaluates conditions common to all currently supported mock frameworks
     * @return true if input field in testedClass can be mocked
     */
    protected boolean isMockableCommonChecks(Field field, Type testedClass) {
        return !field.getType().isPrimitive() && !isWrapperType(field.getType())
                && !field.isOverridden() && !field.getType().isArray() && !field.getType().isEnum()
                && !testSubjectInspector.isNotInjectedInDiClass(field, testedClass) && !isInitInline(field);
    }
}

案例2:下面代码如何优化

/**
 * true - if should stub tested method
 * @param testMethod method being tested
 * @param testedClassFields fields of owner type being tested
 */
@SuppressWarnings("unused")
public boolean shouldStub(Method testMethod, List<Field> testedClassFields) {
    boolean shouldStub = false;
    if (stubMockMethodCallsReturnValues) {
        for (Field testedClassField : testedClassFields) {
            if (isMockable(testedClassField)) {
                LOG.debug("field "+testedClassField.getName()+" type "+testedClassField.getType().getCanonicalName()+" type methods:"+testedClassField.getType().getMethods().size());
                for (Method fieldMethod : testedClassField.getType().getMethods()) {
                    if (fieldMethod.getReturnType() != null && !"void".equals(fieldMethod.getReturnType().getCanonicalName()) && testSubjectInspector.isMethodCalled(fieldMethod, testMethod)) {
                        shouldStub = true;
                        break;
                    }
                }
            }
        }
    }
    LOG.debug("method "+testMethod.getMethodId()+" should be stabbed:"+shouldStub);
    return shouldStub;
}

/**
 * true - if should verify tested method
 * @param testMethod method being tested
 * @param testedClassFields fields of owner type being tested
 */
@SuppressWarnings("unused")
public boolean shouldVerify(Method testMethod, List<Field> testedClassFields) {
    boolean shouldVerity = false;
    if (stubMockMethodCallsReturnValues) {
        for (Field testedClassField : testedClassFields) {
            if (isMockable(testedClassField)) {
                for (Method fieldMethod : testedClassField.getType().getMethods()) {
                    if (fieldMethod.getReturnType() != null && "void".equals(fieldMethod.getReturnType().getCanonicalName()) && testSubjectInspector.isMethodCalled(fieldMethod, testMethod)) {
                        shouldVerity = true;
                        break;
                    }
                }
            }
        }
    }
    LOG.debug("method "+testMethod.getMethodId()+" should be verified:"+shouldVerity);
    return shouldVerity;
}

Method

public class Method { 
    /**
     *
     * true - if method has a return type and not void
     */
    public boolean hasReturn(){
        return returnType != null && !"void".equals(returnType.getName());
    }
}

首先我们看这两方法,其代码复杂度if,for嵌套已经达到了5层;其主体的代码逻辑是很大相似的,因此同样能够复用

优化后

/**
 * true - if should stub tested method
 * @param testMethod method being tested
 * @param testedClassFields fields of owner type being tested
 */
@SuppressWarnings("unused")
public boolean shouldStub(Method testMethod, List<Field> testedClassFields) {
    return callsMockMethod(testMethod, testedClassFields, Method::hasReturn);
}

/**
 * true - if should verify tested method
 * @param testMethod method being tested
 * @param testedClassFields fields of owner type being tested
 */
@SuppressWarnings("unused")
public boolean shouldVerify(Method testMethod, List<Field> testedClassFields) {
    return callsMockMethod(testMethod, testedClassFields, method -> !method.hasReturn());
}

private boolean callsMockMethod(Method testMethod, List<Field> testedClassFields,
    Predicate<Method> mockMethodRelevant) {
    boolean callsMockMethod = false;
    if (!stubMockMethodCallsReturnValues) {
        LOG.debug("method " + testMethod.getMethodId() + " is calling a mock method:" + callsMockMethod);
        return callsMockMethod;
    }
    for (Field testedClassField : testedClassFields) {
        if (!isMockable(testedClassField)) {
            continue;
        }
        LOG.debug("field " + testedClassField.getName() + " type " + testedClassField.getType().getCanonicalName()
            + " type methods:" + testedClassField.getType().getMethods().size());
        for (Method fieldMethod : testedClassField.getType().getMethods()) {
            if (mockMethodRelevant.test(fieldMethod)
                && testSubjectInspector.isMethodCalled(fieldMethod, testMethod)) {
                return true;
            }
        }
    }
    LOG.debug("method " + testMethod.getMethodId() + " is calling a mock method:" + callsMockMethod);
    return callsMockMethod;
}

二、实际国外大佬是如何评审代码的

背景

我将以一个pr请求为例,看看老外如何审查代码,以此进行参考。https://github.com/wrdv/testme-idea/pull/28

此次pr的背景是,给一个开源项目增加一个新特性,在idea中打开一个对话框,可以让用户自行选择他所需要mock的对象和测试的方法。

第一阶段提交

首先,考虑到需要在对话框中先对测试类中的属性和方法先进行一次过滤清洗,我将原先代码中生成类上下文信息的方法提到了前面以便生成对话框时对上下文信息进行复用。

原代码

@Override
    public void invoke(@NotNull final Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
        LOG.debug("Start CreateTestMeAction.invoke");
        final Module srcModule = ModuleUtilCore.findModuleForPsiElement(element);
        if (srcModule == null) return;
        final PsiClass srcClass = getContainingClass(element);
        if (srcClass == null) return;
        PsiDirectory srcDir = element.getContainingFile().getContainingDirectory();
        final PsiPackage srcPackage = JavaDirectoryService.getInstance().getPackage(srcDir);

        final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
        Module testModule = suggestModuleForTestsReflective(project, srcModule);
        final List<VirtualFile> testRootUrls = computeTestRoots(testModule);
//            final HashSet<VirtualFile> testFolders = new HashSet<VirtualFile>(); //from v14
//        checkForTestRoots(srcModule, testFolders); //from v14
        if (testRootUrls==null|| testRootUrls.isEmpty() && computeSuitableTestRootUrls(testModule).isEmpty()) {
            testModule = srcModule;
            if (!propertiesComponent.getBoolean(CREATE_TEST_IN_THE_SAME_ROOT, false)) {
                if (Messages.showOkCancelDialog(project, "Create test in the same source root?", "No Test Roots Found", Messages.getQuestionIcon()) !=
                        Messages.OK) {
                    return;
                }
                propertiesComponent.setValue(CREATE_TEST_IN_THE_SAME_ROOT, String.valueOf(true));
            }
        }
        final PsiDirectory targetDirectory = targetDirectoryLocator.getOrCreateDirectory(project, srcPackage, testModule);
        if (targetDirectory == null) {
            return;
        }
        LOG.debug("targetDirectory:"+targetDirectory.getVirtualFile().getUrl());
        final ClassNameSelection classNameSelection = generatedClassNameResolver.resolveClassName(project, targetDirectory, srcClass, templateDescriptor);
        if (classNameSelection.getUserDecision() != ClassNameSelection.UserDecision.Abort) {
            final Module finalTestModule = testModule;
            CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                @Override
                public void run() {
                    testMeGenerator.generateTest(
                            new FileTemplateContext(
                                    new FileTemplateDescriptor(templateDescriptor.getFilename()),templateDescriptor.getLanguage(),project, classNameSelection.getClassName(), srcPackage, srcModule, finalTestModule,targetDirectory, srcClass,
                                    new FileTemplateConfig(TestMeConfigPersistent.getInstance().getState())
                            )
                    );
                }
            }, "TestMe Generate Test", this);
        }
        LOG.debug("End CreateTestMeAction.invoke");
    }

修改后核心内容是将testTemplateContextBuilder.build 方法前置了,然后将方法的返回作为参数传给createTestMeDialog,来用作属性和方法过滤的参数

@Override
    public void invoke(@NotNull final Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
        LOG.debug("Start CreateTestMeAction.invoke");
        final Module srcModule = ModuleUtilCore.findModuleForPsiElement(element);
        if (srcModule == null) return;
        final PsiClass srcClass = getContainingClass(element);
        if (srcClass == null) return;
        PsiDirectory srcDir = element.getContainingFile().getContainingDirectory();
        final PsiPackage srcPackage = JavaDirectoryService.getInstance().getPackage(srcDir);

        final PropertiesComponent propertiesComponent = PropertiesComponent.getInstance();
        Module testModule = suggestModuleForTestsReflective(project, srcModule);
        final List<VirtualFile> testRootUrls = computeTestRoots(testModule);
//            final HashSet<VirtualFile> testFolders = new HashSet<VirtualFile>(); //from v14
//        checkForTestRoots(srcModule, testFolders); //from v14
        if (testRootUrls==null|| testRootUrls.isEmpty() && computeSuitableTestRootUrls(testModule).isEmpty()) {
            testModule = srcModule;
            if (!propertiesComponent.getBoolean(CREATE_TEST_IN_THE_SAME_ROOT, false)) {
                if (Messages.showOkCancelDialog(project, "Create test in the same source root?", "No Test Roots Found", Messages.getQuestionIcon()) !=
                        Messages.OK) {
                    return;
                }
                propertiesComponent.setValue(CREATE_TEST_IN_THE_SAME_ROOT, String.valueOf(true));
            }
        }
        final PsiDirectory targetDirectory = targetDirectoryLocator.getOrCreateDirectory(project, srcPackage, testModule);
        if (targetDirectory == null) {
            return;
        }
        LOG.debug("targetDirectory:"+targetDirectory.getVirtualFile().getUrl());
        final ClassNameSelection classNameSelection = generatedClassNameResolver.resolveClassName(project, targetDirectory, srcClass, templateDescriptor);
        if (classNameSelection.getUserDecision() == ClassNameSelection.UserDecision.Abort) {
            return;
        }
        final Module finalTestModule = testModule;
        FileTemplateContext fileTemplateContext = new FileTemplateContext(
            new FileTemplateDescriptor(templateDescriptor.getFilename()), templateDescriptor.getLanguage(), project,
            classNameSelection.getClassName(), srcPackage, srcModule, finalTestModule, targetDirectory, srcClass,
            new FileTemplateConfig(TestMeConfigPersistent.getInstance().getState()));

        TestTemplateContextBuilder testTemplateContextBuilder =  new TestTemplateContextBuilder(new MockBuilderFactory(), new MethodReferencesBuilder());
        FileTemplateManager fileTemplateManager = TestMeTemplateManager.getInstance(fileTemplateContext.getTargetDirectory().getProject());
        final Map<String, Object> templateCtxtParams = testTemplateContextBuilder.build(fileTemplateContext, fileTemplateManager.getDefaultProperties());

        boolean openUserCheckDialog = Objects.requireNonNull(TestMeConfigPersistent.getInstance().getState()).isOpenUserCheckDialog();
        if (openUserCheckDialog) {
            // create filed and method check dialog
            final CreateTestMeDialog dialog = createTestMeDialog(project, fileTemplateContext.getSrcClass(),
                fileTemplateContext.getFileTemplateDescriptor().getDisplayName(), templateCtxtParams);
            // if not ok button selected the return
            if (dialog.isModal() && !dialog.showAndGet()) {
                return;
            }
        }

        CommandProcessor.getInstance().executeCommand(project, new Runnable() {
            @Override
            public void run() {
                testMeGenerator.generateTest(fileTemplateContext, templateCtxtParams);
            }
        }, "TestMe Generate Test", this);
        LOG.debug("End CreateTestMeAction.invoke");
    }

在对话框中的initExtractingClassMembers方法中,根据传递过来的上下文templateCtxtParams来对属性和方法过滤

/**
 *
 * dialog for user to check fields mockable and methods testable
 *
 * @author huangliang
 */
public class CreateTestMeDialog extends DialogWrapper {

    private final Map<String, Object> templateCtxtParams;
    private final PsiClass myTargetClass;
    private final String templateFileName;
    private final MemberSelectionTable myMethodsTable = new MemberSelectionTable(Collections.emptyList(), null);
    private final MemberSelectionTable myFieldsTable = new MemberSelectionTable(Collections.emptyList(), null);
    private final List<String> checkedFieldNameList = new ArrayList<>();
    private final List<String> checkedMethodIdList = new ArrayList<>();
    
    public CreateTestMeDialog(@NotNull Project project, @NotNull @NlsContexts.DialogTitle String title,
        PsiClass targetClass, String templateFileName, Map<String, Object> templateCtxtParams) {
        super(project, true);
        myTargetClass = targetClass;
        this.templateCtxtParams = templateCtxtParams;
        this.templateFileName = templateFileName;
        setTitle(title);
        init();
    }

    @Override
    protected JComponent createCenterPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        JLabel fieldLabel = new JLabel("Select Need Mocked Fields");
        panel.add(fieldLabel);
        panel.add(ScrollPaneFactory.createScrollPane(myFieldsTable));

        JLabel methodLabel = new JLabel("Select Need Test Methods");
        panel.add(methodLabel);
        panel.add(ScrollPaneFactory.createScrollPane(myMethodsTable));

        initExtractingClassMembers();
        return panel;
    }

    /**
     * update field is mockable or not after user checked
     */
    private void updateClassMockableFields() {
        Collection<MemberInfo> selectedMemberInfos = myFieldsTable.getSelectedMemberInfos();
        List<String> userCheckedMockableFieldsList =
            selectedMemberInfos.stream().map(e -> e.getMember().getName()).toList();
        Type classTypeObj = (Type)templateCtxtParams.get(TestMeTemplateParams.TESTED_CLASS);
        for (Field field : classTypeObj.getFields()) {
            if (userCheckedMockableFieldsList.contains(field.getName())) {
                checkedFieldNameList.add(field.getName());
            }
        }
        templateCtxtParams.put(TestMeTemplateParams.USER_CHECKED_MOCK_FIELDS, checkedFieldNameList);
    }

    /**
     * update method is testable or not after user checked
     */
    private void updateClassTestableMethods() {
        Collection<MemberInfo> selectedMemberInfos = myMethodsTable.getSelectedMemberInfos();
        List<String> testableMethodList =
            selectedMemberInfos.stream().map(e -> PsiMethodUtils.formatMethodId((PsiMethod)e.getMember())).toList();
        Type classTypeObj = (Type)templateCtxtParams.get(TestMeTemplateParams.TESTED_CLASS);
        for (Method method : classTypeObj.getMethods()) {
            if (testableMethodList.contains(method.getMethodId())) {
                checkedMethodIdList.add(method.getMethodId());
            }
        }
        templateCtxtParams.put(TestMeTemplateParams.USER_CHECKED_TEST_METHODS, checkedMethodIdList);
    }

    /**
     * init and extract class fields and methods for user to check
     */
    public void initExtractingClassMembers() {
        Set<PsiClass> classes= InheritanceUtil.getSuperClasses(myTargetClass);
        classes.add(myTargetClass);

        Type classTypeObj = (Type)templateCtxtParams.get(TestMeTemplateParams.TESTED_CLASS);
        TestSubjectInspector testSubjectUtil =
            (TestSubjectInspector)templateCtxtParams.get(TestMeTemplateParams.TestSubjectUtils);
        MockBuilder templateMockBuilder = getTemplateMockBuilder(templateFileName);

        // build field mockable map, key = field name, value = true - if field mockable
        Map<String, Boolean> fieldMockableMap = new HashMap<>();
        for (Field field : classTypeObj.getFields()) {
            Boolean fieldMockable = templateMockBuilder.isMockable(field, classTypeObj);
            fieldMockableMap.put(field.getName(), fieldMockable);
        }
        // build method testable map, key = method id, value = true - if method testable
        Map<String, Boolean> methodTestableMap = new HashMap<>();
        for (Method method : classTypeObj.getMethods()) {
            Boolean testable = testSubjectUtil.shouldBeTested(method);
            methodTestableMap.put(method.getMethodId(), testable);
        }

        // init method table and field table
        List<MemberInfo> methodResult = new ArrayList<>();
        List<MemberInfo> fieldResult = new ArrayList<>();
        for (PsiClass aClass : classes) {
            if (CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName()))
                continue;
            initMethodsTable(aClass, methodResult, methodTestableMap);
            initFieldsTable(aClass, fieldResult, fieldMockableMap);
        }
    }

    /**
     * get mock builder for template
     * @param templateFileName template name
     * @return MockBuilder
     */
    private MockBuilder getTemplateMockBuilder(String templateFileName) {
        if (TemplateRegistry.JUNIT4_POWERMOCK_JAVA_TEMPLATE.equals(templateFileName)) {
            return (PowerMockBuilder)templateCtxtParams.get(TestMeTemplateParams.PowerMockBuilder);
        } else {
            return (MockitoMockBuilder)templateCtxtParams.get(TestMeTemplateParams.MockitoMockBuilder);
        }
    }

    private void initMethodsTable(PsiClass myTargetClass, List<MemberInfo> result, Map<String, Boolean> methodTestableMap) {
        Set<PsiMember> selectedMethods = new HashSet<>();
        MemberInfo.extractClassMembers(myTargetClass, result, member -> {
            if (!(member instanceof PsiMethod method))
                return false;
            String methodId = PsiMethodUtils.formatMethodId(method);
            if (methodTestableMap.containsKey(methodId) && methodTestableMap.get(methodId)) {
                selectedMethods.add(member);
                return true;
            }
            return false;
        }, false);

        for (MemberInfo each : result) {
            each.setChecked(selectedMethods.contains(each.getMember()));
        }

        myMethodsTable.setMemberInfos(result);
    }

    private void initFieldsTable(PsiClass myTargetClass, List<MemberInfo> result, Map<String, Boolean> fieldMockableMap) {
        Set<PsiMember> selectedFields = new HashSet<>();
        MemberInfo.extractClassMembers(myTargetClass, result, member -> {
            if (!(member instanceof PsiField field))
                return false;
            if (fieldMockableMap.containsKey(field.getName()) && fieldMockableMap.get(field.getName())) {
                selectedFields.add(member);
                return true;
            }
            return false;
        }, false);

        for (MemberInfo each : result) {
            each.setChecked(selectedFields.contains(each.getMember()));
        }

        myFieldsTable.setMemberInfos(result);
    }

    @Override
    protected String getDimensionServiceKey() {
        return getClass().getName();
    }

    @Override
    protected void doOKAction() {
        updateClassMockableFields();
        updateClassTestableMethods();
        super.doOKAction();
    }

}

按照这种写法,正常实现了打开对话框并让用户自定义mock的属性和选择测试方法的功能。

第一阶段修改意见

我们看下国外的大佬的修改意见

从交互上考虑,由于构建上文文信息的方法是一个非常重的操作,耗时比较长,如果将上下文构建的方法提前的化,会导致对话框打开延迟时间很长,造成用户体验不好,因此他要求寻找另一种方法来实现属性和方法的初始化。

针对命名的修改

完备性考虑,考虑对空属性列表的问题,他建议如果为空不展示选择属性的区域。

第二阶段提交

按照他的意见,我对代码进行了修改,将上下文信息的初始化还原了,重新修改了属性和方法的初始化过滤方法

/**
 *
 * dialog for user to check fields mockable and methods testable
 *
 * @author huangliang
 */
public class CustomizeTestDialog extends DialogWrapper {

    private final PsiClass myTargetClass;
    private final MemberSelectionTable myMethodsTable = new MemberSelectionTable(Collections.emptyList(), null);
    private final MemberSelectionTable myFieldsTable = new MemberSelectionTable(Collections.emptyList(), null);
    private final FileTemplateContext fileTemplateContext;

    public CustomizeTestDialog(@NotNull Project project, @NotNull @NlsContexts.DialogTitle String title,
        PsiClass targetClass, FileTemplateContext fileTemplateContext) {
        super(project, true);
        myTargetClass = targetClass;
        this.fileTemplateContext = fileTemplateContext;
        setTitle(title);
        init();
    }

    @Override
    protected JComponent createCenterPanel() {
        initExtractingClassMembers();

        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        if (myFieldsTable.getRowCount() > 0) {
            JLabel fieldLabel = new JLabel("Mock fields:");
            panel.add(fieldLabel);
            panel.add(ScrollPaneFactory.createScrollPane(myFieldsTable));
        }

        JLabel methodLabel = new JLabel("Test Methods:");
        panel.add(methodLabel);
        panel.add(ScrollPaneFactory.createScrollPane(myMethodsTable));

        return panel;
    }

    /**
     * update field is mockable or not after user checked
     */
    private void updateClassMockableFields() {
        Collection<MemberInfo> selectedMemberInfos = myFieldsTable.getSelectedMemberInfos();
        List<String> userCheckedMockableFieldsList =
            selectedMemberInfos.stream().map(e -> e.getMember().getName()).toList();
        fileTemplateContext.getFileTemplateCustomization().getSelectedFieldNameList()
            .addAll(userCheckedMockableFieldsList);
    }

    /**
     * update method is testable or not after user checked
     */
    private void updateClassTestableMethods() {
        Collection<MemberInfo> selectedMemberInfos = myMethodsTable.getSelectedMemberInfos();
        List<String> testableMethodList =
            selectedMemberInfos.stream().map(e -> PsiMethodUtils.formatMethodId((PsiMethod)e.getMember())).toList();
        fileTemplateContext.getFileTemplateCustomization().getSelectedMethodIdList().addAll(testableMethodList);
    }

    /**
     * init and extract class fields and methods for user to check
     */
    public void initExtractingClassMembers() {
        Set<PsiClass> classes;
        if (fileTemplateContext.getFileTemplateConfig().isGenerateTestsForInheritedMethods()) {
            classes = InheritanceUtil.getSuperClasses(myTargetClass);
            classes.add(myTargetClass);
        } else {
            classes = Collections.singleton(myTargetClass);
        }

        // init method table and field table
        List<MemberInfo> methodResult = new ArrayList<>();
        for (PsiClass aClass : classes) {
            if (CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName()))
                continue;
            initMethodsTable(aClass, methodResult);
        }

        List<MemberInfo> fieldResult = new ArrayList<>();
        initFieldsTable(myTargetClass, fieldResult, fileTemplateContext.getFileTemplateDescriptor().getDisplayName());
    }

    /**
     *
     * @param templateFileName template name
     * @return true - if final can be mocked
     */
    private boolean canMockFinal(String templateFileName) {
        return TemplateRegistry.JUNIT4_POWERMOCK_JAVA_TEMPLATE.equals(templateFileName);
    }

    private void initMethodsTable(PsiClass myTargetClass, List<MemberInfo> result) {
        Set<PsiMember> selectedMethods = new HashSet<>();
        MemberInfo.extractClassMembers(myTargetClass, result, member -> {
            if (!(member instanceof PsiMethod method))
                return false;
            if (shouldBeTested(method, myTargetClass)) {
                selectedMethods.add(member);
                return true;
            }
            return false;
        }, false);

        for (MemberInfo each : result) {
            each.setChecked(selectedMethods.contains(each.getMember()));
        }

        myMethodsTable.setMemberInfos(result);
    }

    private void initFieldsTable(PsiClass myTargetClass, List<MemberInfo> result, String templateFileName) {
        Set<PsiMember> selectedFields = new HashSet<>();
        MemberInfo.extractClassMembers(myTargetClass, result, member -> {
            if (!(member instanceof PsiField field))
                return false;
            if (isMockable(field, myTargetClass, templateFileName)) {
                selectedFields.add(member);
                return true;
            }
            return false;
        }, false);

        for (MemberInfo each : result) {
            each.setChecked(selectedFields.contains(each.getMember()));
        }

        myFieldsTable.setMemberInfos(result);
    }

    @Override
    protected String getDimensionServiceKey() {
        return getClass().getName();
    }

    @Override
    protected void doOKAction() {
        updateClassMockableFields();
        updateClassTestableMethods();
        super.doOKAction();
    }

    public boolean isMockable(PsiField psiField, PsiClass testedClass, String templateFileName) {
        boolean overridden = Field.isOverriddenInChild(psiField, testedClass);
        boolean isFinal =
            psiField.getModifierList() != null && psiField.getModifierList().hasExplicitModifier(PsiModifier.FINAL);
        boolean isPrimitive = psiField.getType() instanceof PsiPrimitiveType;
        PsiClass psiClass = PsiUtil.resolveClassInType(psiField.getType());
        String canonicalText = JavaTypeUtils.resolveCanonicalName(psiClass, null);
        boolean isArray = ClassNameUtils.isArray(canonicalText);
        boolean isEnum = JavaPsiTreeUtils.resolveIfEnum(psiClass);
        boolean isMockitoMockMakerInlineOn = MockBuilderFactory.isMockInline(fileTemplateContext);
        boolean isWrapperType = MockitoMockBuilder.WRAPPER_TYPES.contains(canonicalText);
        return !isPrimitive && !isWrapperType
            && (canMockFinal(templateFileName) || !isFinal || isMockitoMockMakerInlineOn) && !overridden && !isArray
            && !isEnum;
    }

    /**
     * true - method should test
     */
    public boolean shouldBeTested(PsiMethod method, PsiClass psiClass) {
        return MethodFactory.isTestable(method, psiClass);
    }

}

第二阶段修改意见

看下新的修改意见

首先他认为,用户可以自己选择需要测试的方法了,因此可以不在添加生成父类测试方法配置的判断,建议天剑checkbox来让用户自行选择,同时他查看了InheritanceUtil的文档,认为InheritanceUtil.getSuperClasses(myTargetClass, classes, false)这个方法更好,调用这个方法无需进一步剔除非本工程的父类

修改入参的方式不是最佳的实践方式,虽然jetbrain idea 官方源码中是这么写的,但是并不推荐这么修改。

第三阶段修改

public class CustomizeTestDialog extends DialogWrapper {

    private final MemberSelectionTable myMethodsTable;
    private final MemberSelectionTable myFieldsTable;
    private final FileTemplateContext fileTemplateContext;

    public CustomizeTestDialog(@NotNull Project project, @NotNull @NlsContexts.DialogTitle String title,
        PsiClass targetClass, FileTemplateContext fileTemplateContext) {
        super(project, true);
        this.fileTemplateContext = fileTemplateContext;
        myMethodsTable = new MemberSelectionTable(initMethodsTable(targetClass), null);
        myFieldsTable = new MemberSelectionTable(
            initFieldsTable(targetClass, fileTemplateContext.getFileTemplateDescriptor().getDisplayName()), null);
        setTitle(title);
        init();
    }

    @Override
    protected JComponent createCenterPanel() {
        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        if (myFieldsTable.getRowCount() > 0) {
            JLabel fieldLabel = new JLabel("Mock fields:");
            panel.add(fieldLabel);
            panel.add(ScrollPaneFactory.createScrollPane(myFieldsTable));
        }
        JLabel methodLabel = new JLabel("Test Methods:");
        panel.add(methodLabel);
        panel.add(ScrollPaneFactory.createScrollPane(myMethodsTable));

        return panel;
    }

    /**
     * update field is mockable or not after user checked
     */
    private void updateClassMockableFields() {
        Collection<MemberInfo> selectedMemberInfos = myFieldsTable.getSelectedMemberInfos();
        List<String> userCheckedMockableFieldsList =
            selectedMemberInfos.stream().map(e -> e.getMember().getName()).toList();
        fileTemplateContext.getFileTemplateCustomization().getSelectedFieldNameList()
            .addAll(userCheckedMockableFieldsList);
    }

    /**
     * update method is testable or not after user checked
     */
    private void updateClassTestableMethods() {
        Collection<MemberInfo> selectedMemberInfos = myMethodsTable.getSelectedMemberInfos();
        List<String> testableMethodList =
            selectedMemberInfos.stream().map(e -> PsiMethodUtils.formatMethodId((PsiMethod)e.getMember())).toList();
        fileTemplateContext.getFileTemplateCustomization().getSelectedMethodIdList().addAll(testableMethodList);
    }

    /**
     *
     * @param templateFileName template name
     * @return true - if final can be mocked
     */
    private boolean canMockFinal(String templateFileName) {
        return TemplateRegistry.JUNIT4_POWERMOCK_JAVA_TEMPLATE.equals(templateFileName);
    }

    private List<MemberInfo> initMethodsTable(PsiClass myTargetClass) {
        Set<PsiClass> classes = new HashSet<>();
        InheritanceUtil.getSuperClasses(myTargetClass, classes, false);
        classes.add(myTargetClass);

        Set<PsiMember> selectedMethods = new HashSet<>();
        List<MemberInfo> result = new ArrayList<>();
        // init method table and field table
        for (PsiClass aClass : classes) {
            MemberInfo.extractClassMembers(aClass, result, member -> {
                if (!(member instanceof PsiMethod method))
                    return false;
                if (shouldBeTested(method, myTargetClass)) {
                    selectedMethods.add(member);
                    return true;
                }
                return false;
            }, false);
        }
        for (MemberInfo each : result) {
            each.setChecked(selectedMethods.contains(each.getMember()));
        }
        return result;
    }

    private List<MemberInfo> initFieldsTable(PsiClass myTargetClass, String templateFileName) {
        Set<PsiMember> selectedFields = new HashSet<>();
        List<MemberInfo> result = new ArrayList<>();
        MemberInfo.extractClassMembers(myTargetClass, result, member -> {
            if (!(member instanceof PsiField field))
                return false;
            if (isMockable(field, myTargetClass, templateFileName)) {
                selectedFields.add(member);
                return true;
            }
            return false;
        }, false);

        for (MemberInfo each : result) {
            each.setChecked(selectedFields.contains(each.getMember()));
        }
        return result;
    }

    @Override
    protected String getDimensionServiceKey() {
        return getClass().getName();
    }

    @Override
    protected void doOKAction() {
        updateClassMockableFields();
        updateClassTestableMethods();
        super.doOKAction();
    }

    private boolean isMockable(PsiField psiField, PsiClass testedClass, String templateFileName) {
        boolean isPrimitive = psiField.getType() instanceof PsiPrimitiveType;
        // make a direct return if isPrimitive, because PsiUtil.resolveClassInType(psiField.getType()) returns null
        if (isPrimitive) {
            return false;
        }
        boolean overridden = Field.isOverriddenInChild(psiField, testedClass);
        if (overridden) {
            return false;
        }
        boolean isFinal =
            psiField.getModifierList() != null && psiField.getModifierList().hasExplicitModifier(PsiModifier.FINAL);
        boolean isMockitoMockMakerInlineOn = MockBuilderFactory.isMockInline(fileTemplateContext);
        if (!(canMockFinal(templateFileName) || !isFinal || isMockitoMockMakerInlineOn)) {
            return false;
        }
        PsiClass psiClass = PsiUtil.resolveClassInType(psiField.getType());
        boolean isEnum = JavaPsiTreeUtils.resolveIfEnum(psiClass);
        if (isEnum) {
            return false;
        }
        String canonicalText = JavaTypeUtils.resolveCanonicalName(psiClass, null);
        return (null != canonicalText) && !MockitoMockBuilder.WRAPPER_TYPES.contains(canonicalText)
            && !ClassNameUtils.isArray(canonicalText);
    }

    /**
     * true - method should test
     */
    private boolean shouldBeTested(PsiMethod method, PsiClass psiClass) {
        return MethodFactory.isTestable(method, psiClass);
    }
}

代码修改玩完后更加简洁

三、总结

除了开头简述的那些需要评审的内容外,往往还有许多其他的方面也要考虑。特别是在复杂系统的场景下,如:

1、异常、边界场景是否考虑,以及如何处理

2、是否能够满足特定场景的并发流量,如果涉及分库、分表、缓存等设计是否合理。

3、是否会影响已有的功能、能否向下兼容,如果出现问题如何快速恢复。

等等。这就要求我们平时多思考,多积累对应的经验。

另外打个广告,欢迎大家搜所并下载自动生成单元测试的 jetbrain Idea 开源插件 TestMe,并给我们反馈问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1573725.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣22. 括号生成

Problem: 22. 括号生成 文章目录 题目描述思路复杂度Code 题目描述 思路 1.定义回溯函数&#xff1a;void backtrack(int n, int leftUsed, int rightUsed, int k, string& path)&#xff1b;(每个参数的具体说明见下面代码) 1.1.结束条件&#xff1a;当k 2 * n时将path添…

SQLyog连接数据库8.0版本解析错误问题解决方案

问题描述&#xff1a; 解决方案&#xff1a; alter userrootlocalhostidentified with mysql_native_password by 密码; 再次连接就可以了。

zdpdjango_argonadmin Django后台管理系统中的常见功能开发

效果预览 首先&#xff0c;看一下这个项目最开始的样子&#xff1a; 左侧优化 将左侧优化为下面的样子&#xff1a; 代码位置&#xff1a; 代码如下&#xff1a; {% load i18n static admin_argon %}<aside class"sidenav bg-white navbar navbar-vertical na…

华为OD机试 - 全排列 - 回溯(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

【Python时序预测系列】基于ACO+LSTM实现单变量时间序列预测(源码)

这是我的第253篇原创文章。 一、引言 蚁群优化&#xff08;Ant Colony Optimization&#xff0c;ACO&#xff09;是一种启发式算法&#xff0c;受到蚂蚁寻找食物的行为启发而来。它可以用于优化问题&#xff0c;包括调整神经网络的超参数。长短期记忆网络&#xff08;LSTM&…

wheeltec轮趣ROS教育机器人的网络连接

一、术语解析 宿主机&#xff1a;宿主机是指物理主机&#xff0c;比如用于开发测试的笔记本电脑和台式机电脑。 虚拟机&#xff1a;虚拟机是指安装在宿主机的VMware&#xff0c;推荐在宿主机上安装虚拟机&#xff0c;官方提供虚拟机的镜像以及配套的开发环境。 ROS主机&…

HTML:表单

案例&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>报名表</title> </head> <body><form action"demo/welcome.php" method"post">名字&#xff1a;<inpu…

【Linux】进程初步理解

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 冯诺依曼体系结构1.1 认识冯诺依曼体系结构1.2 存储金字塔 2. 操作系统2.1 概念2.2 结构2.3 操作系统的管理 3. 进程3.1 进程描述3.2 Linux下的PCB 4. task_struct本身内部属性4.1 启动4.2 进程的创建方式4.2.1 父…

如何不编程用 ChatGPT 爬取网站数据?

敢于大胆设想&#xff0c;才能在 AI 时代提出好问题。 需求 很多小伙伴&#xff0c;都需要为研究获取数据。从网上爬取数据&#xff0c;是其中关键一环。以往&#xff0c;这都需要编程来实现。 可最近&#xff0c;一位星友在知识星球提问&#xff1a; 这里涉及到一些个人隐私&a…

秋招刷题4(动态规划)

1.购物单 import java.util.Scanner;public class Main {public static void main(String[] args){Scanner sc new Scanner(System.in);int N sc.nextInt();int m sc.nextInt();Goods[] goods new Goods[m];for(int i 0; i < m; i){goods[i] new Goods();}for(int i …

Dev c++ C语言实现第一个 dll 动态链接库 创建与调用

代码来源&#xff1a; 极简版 C 动态链接库&#xff08;DLL&#xff09;创建与调用 - 知乎 (zhihu.com) 现在移植到Devc 里 首先创建DLL 项目&#xff0c;如果不创建&#xff0c;直接粘贴代码编译不通过&#xff0c;应该是项目里指定了链接类型。 如图&#xff1a; 然后选择…

【数据结构(二)】顺序表与ArrayList

❣博主主页: 33的博客❣ ▶文章专栏分类:数据结构◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你学更多数据结构知识 目录 1.前言2.定义IList接口3.MyArraylist实现接口3.1定义成员变量与构造方法3.2添加元素3.3…

Jackson 2.x 系列【14】特征配置篇之 MapperFeature

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 前言2. 通用2.1 USE_ANNOTATIONS2.2 USE_GETTERS_AS_SETTERS2.3 PROPAGATE_TR…

c 语言 指数搜索(Exponential Search)

该搜索算法的名称可能会产生误导&#xff0c;因为它的工作时间为 O(Log n)。该名称来自于它搜索元素的方式。 给定一个已排序的数组和要 搜索的元素 x&#xff0c;找到 x 在数组中的位置。 输入&#xff1a;arr[] {10, 20, 40, 45, 55} x 45 输出&#xff1a;在索…

多语言婚恋交友APP开发的关键成功因素

随着移动互联网的快速发展&#xff0c;多语言婚恋交友APP的需求和发展逐渐成为了一个备受关注的话题。在全球范围内&#xff0c;人们希望通过移动应用来寻找爱情、建立关系和拓展社交圈子&#xff0c;因此开发一款具有全球影响力的多语言婚恋交友APP成为了许多开发者的目标。针…

支持编写任何类型的爬虫:基于 Golang 的优雅爬虫框架 | 开源日报 No.216

gocolly/colly Stars: 21.5k License: Apache-2.0 colly 是 Golang 的优雅爬虫和爬虫框架。 该项目提供了一个清晰的接口&#xff0c;用于编写任何类型的爬虫/抓取器/蜘蛛。Colly 可以轻松从网站中提取结构化数据&#xff0c;可用于数据挖掘、数据处理或存档等各种应用。 其主…

使用免费开源AI平台:OCR识别抖音短视频及网络图片中文字内容(可本地部署)

在数字化时代&#xff0c;信息的快速获取和处理变得尤为重要。网络图片文字识别技术作为一项重要的人工智能应用&#xff0c;已经在多个领域展现出其独特的价值。本文将基于思通数科AI开放平台提供的网络图片文字识别服务&#xff0c;探讨该技术的应用场景、特色优势以及如何有…

超声波清洗机哪家强?超声波清洗机排行榜!最强超声波清洗机推荐

眼镜作为日常生活中不可或缺的用品&#xff0c;对于很多人来说是必备的。然而&#xff0c;随着使用时间的增长&#xff0c;眼镜表面往往会沾染灰尘、污垢等&#xff0c;这不仅影响了镜片的透光性&#xff0c;也可能影响到使用者的视力和舒适度。因此&#xff0c;清洁眼镜成了一…

PowerShell正则表达式匹配文件内容并输出到屏幕(或保存到文件)

代码&#xff1a; foreach ($line in Get-Content -path .\test.sql) { if ($line -match bdw_\w*.\w*) {write-output $matches[0]}}思路&#xff1a; 读取文件并遍历 foreach ($line in Get-Content -path .\test.sql) 正则匹配 if ($line -match ‘bdw_\w*.\w*’) 这个匹配…