The Rule of Method Design

本文探讨了由Jeffrey Palermo提出的面向对象设计中方法设计的七大准则,包括明确的方法命名、单一职责原则、合理的方法长度及参数数量限制等,并讨论了不同编程语言下方法设计的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        读了Jeffrey Palermo 的随笔 How to design a single method后,对他所述的方法(Method)设计的准则大部分比较赞同。方法的设计是类的设计的一部分(在面向对象设计的前提下),是比较细节的问题,但也是关乎设计是否优秀的细节。
Jeffrey Palermo 提到了下面的一些准则(斜体部分是我的理解):

1、 方法的命名应清楚地表达出此方法所做的事情。

命名其实也是一门学问——看起来简单,但往往比较有经验的设计师才能作出比较合适的命名。不当的命名常常让他人误会,带来理解上的困难。一般方法的命名使用动词或者动词+名词。比如Append、Push、ShowDialog、GetName等。

2、 一个方法做且只做一件事情。

有人常常一个方法中好几件事情,这是没有正确划分各个方法职责的结果。做一件比较复杂的事情,可能需要分为几个步骤来完成,我们把它拆为几个方法(一般为private的方法),每个方法完成其中一个步骤。当然,这需要根据实际情况做合适的划分。

3、 一个方法要么做一些事情,要么返回一些东西。如果该方法返回一些东西的话,调用它无数次也不应当产生负面影响。

4、 一个方法所占的篇幅不应超出一屏。

这是经验之谈,如果一个方法里面你写了大量的代码(比如超过50行),那该方法需要重构了。Jeffrey Palermo 甚至说一个方法最好少于10行。

5、 方法的参数要少。

如果一个方法必须大量的参数,那可以将这些信息封装到一个Object中,现成的例子是.net framework中的ProcessStartInfo。个人认为一个方法的参数最好不超过5个。

6、 方法仅仅依赖于方法的参数,或者由构造函数传入的类成员。

7、 在合适的地方捕捉异常。

在需要对异常进行处理的地方捕捉异常,否则让其传播。

对于方法的设计准则,在不同的语言中可能会不尽相同。比如在.net中会对传入的参数做一些检查,而在native C/C++的代码中(比如wcscpy函数)总是认为调用者会保证参数正确,而根本不作任何检查,看下面代码:
wchar_t  *  __cdecl wcscpy(wchar_t  *  dst,  const  wchar_t  *  src)
{
        wchar_t 
* cp = dst;

        
while*cp++ = *src++ )
                ;               
/* Copy src over dst */

        
return( dst );
}

而在c#中:
public   static   string  Copy( string  str)
{
      
if (str == null)
      
{
            
throw new ArgumentNullException("str");
      }

      
string text1 = string.FastAllocateString(str.Length);
      
string.FillString(text1, 0, str);
      
return text1;
}


 

准则归准则,在实际开发中,还是要看具体情况。


以下部分是网友的评论:

1. 方法仅仅依赖于方法的参数,或者由构造函数传入的类成员。
不解,难道方法就不会调用别的方法,调用另外的方法也是一种依赖
---------------
这里的依赖不是指方法之间的依赖。即使调用了别的方法,那传给那个方法的参数也是本方法中产生的值,或者本方法的参数,或者由构造函数传入的类成员。

2. 在合适的地方捕捉异常。
如果你的方法不希望调用者获得最底层的异常信息,那么应该捕捉处理,并根据需要包装后抛出新异常,不论如何,异常到了系统的顶部.都必须得到处理,而不应该抛给用户.
------------------
你说的不错。当我们需要做处理或者要添加一些值的时候,我们才捕捉异常,否则让他传播。

plugins { id 'com.android.application' } apply from: 'jacoco-report.gradle' def cfg = rootProject.ext.configuration android { compileSdk cfg.targetSdk defaultConfig { applicationId cfg.applicationId minSdk cfg.minSdk targetSdk cfg.targetSdk versionCode cfg.versionCode versionName cfg.versionName } signingConfigs { release { storeFile file("$rootDir/platform.jks") storePassword = "123456" keyPassword = "123456" keyAlias = "ktplatfrom" } debug { storeFile file("$rootDir/platform.jks") storePassword = "123456" keyPassword = "123456" keyAlias = "ktplatfrom" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debugCoverage { minifyEnabled false testCoverageEnabled false } } // buildTypes { // release { // minifyEnabled false // signingConfig signingConfigs.release // } // debug { // minifyEnabled false // signingConfig signingConfigs.debug // debuggable true // testCoverageEnabled false // } // } applicationVariants.all { variant -> variant.outputs.all { output -> def newName = "ktLauncher.apk" outputFileName = newName } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } dataBinding { enabled = true } buildFeatures { aidl true } //儿童模式 flavorDimensions "default" productFlavors { _NORMAL_MODE { buildConfigField("int", "user_model", "0") } _CHILD_MODE { buildConfigField("int", "user_model", '1') } } testOptions { unitTests { includeAndroidResources = true returnDefaultValues = true } } } gradle.projectsEvaluated { tasks.withType(JavaCompile) { Set<File> fileSet = options.bootstrapClasspath.getFiles() List<File> newFileList = new ArrayList<>(); //将framework.jar插入到最前面 newFileList.add(new File("libs/framework.jar")) //最后将原始的数据插入 newFileList.addAll(fileSet) options.bootstrapClasspath = files( newFileList.toArray() ) } } dependencies { // api fileTree(include: ['*.jar'], dir: 'libs') implementation rootProject.ext.dependencies["appcompat"] implementation rootProject.ext.dependencies["material"] implementation rootProject.ext.dependencies["constraintlayout"] implementation rootProject.ext.dependencies["lifecycle-extensions"] implementation rootProject.ext.dependencies["gson"] implementation project(':base') compileOnly files('libs/framework.jar') implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.android.support:design:28.0.0' implementation "androidx.constraintlayout:constraintlayout:2.1.3" annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' // test testImplementation 'org.powermock:powermock-module-junit4:2.0.7' testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.4' testImplementation 'org.powermock:powermock-api-mockito2:2.0.7' testImplementation 'org.mockito:mockito-core:3.3.3' testImplementation 'android.arch.core:core-testing:1.1.1' testImplementation 'org.mockito:mockito-inline:3.11.2' } -----build.gradle----------- apply plugin: 'jacoco' //gradlew clean & gradlew createOfflineTestCoverageReport & gradlew jacocoTestReport tasks.withType(Test) { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } configurations { jacocoAnt jacocoRuntime } jacoco { toolVersion = "0.8.5" } def offline_instrumented_outputDir = "$buildDir.path/intermediates/classes-instrumented/debugCoverage" tasks.withType(Test) { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } def coverageSourceDirs = [ 'src/main/java' ] task jacocoTestReport(type: JacocoReport, dependsOn: "test") { group = "Reporting" description = "Generate Jacoco coverage reports" classDirectories.from = fileTree( dir: './build/intermediates/javac/debugCoverage/classes/', excludes: [ '**/R.class', '**/R$*.class', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*', '**/BuildConfig.*', '**/**Bean.class', '**/inputmethod/*', '**/config/*.*', '**/flex/*.*', '**/AssetsUtils.class' ] ) getSourceDirectories().setFrom(files(coverageSourceDirs)) getExecutionData().setFrom(files('./build/jacoco/testDebugUnitTest.exec')) } jacocoTestReport { reports { xml.enabled true html.enabled true html.destination file("build/test-results/jacocoHtml") } } /* This task is used to create offline instrumentation of classes for on-the-fly instrumentation coverage tool like Jacoco. See jacoco classId * and Offline Instrumentation from the jacoco site for more info. * * In this case, some classes mocked using PowerMock were reported as 0% coverage on jacoco & Sonarqube. The issue between PowerMock and jacoco * is well documented, and a possible solution is offline Instrumentation (not so well documented for gradle). * * In a nutshell, this task: * - Pre-instruments the original *.class files * - Puts the instrumented classes path at the beginning of the task's classpath (for report purposes) * - Runs test & generates a new exec file based on the pre-instrumented classes -- as opposed to on-the-fly instrumented class files generated by jacoco. * * It is currently not implemented to run prior to any other existing tasks (like test, jacocoTestReport, etc...), therefore, it should be called * explicitly if Offline Instrumentation report is needed. * * Usage: gradle clean & gradle createOfflineInstrTestCoverageReport & gradle jacocoTestReport * - gradle clean //To prevent influence from any previous task execution * - gradle createOfflineInstrTestCoverageReport //To generate *.exec file from offline instrumented class * - gradle jacocoTestReport //To generate html report from newly created *.exec task */ task createOfflineTestCoverageReport(dependsOn: ['instrument', 'testDebugCoverageUnitTest']) { group = "reporting" doLast { ant.taskdef(name: 'report', classname: 'org.jacoco.ant.ReportTask', classpath: configur3+ations.jacocoAnt.asPath) ant.report() { executiondata { ant.file(file: "$buildDir.path/jacoco/testDebugCoverageUnitTest.exec") } structure(name: 'Example') { classfiles { fileset(dir: "$project.buildDir/intermediates/javac/debugCoverage") } sourcefiles { fileset(dir: 'src/main/java') } } //Uncomment if we want the task to generate jacoco html reports. However, the current script does not exclude files. //An alternative is to used jacocoTestReport after this task finishes //html(destdir: "$buildDir.path/reports/jacocoHtml") } } } createOfflineTestCoverageReport.dependsOn(clean) /* * Part of the Offline Instrumentation process is to add the jacoco runtime to the class path along with the path of the instrumented files. */ gradle.taskGraph.whenReady { graph -> if (graph.hasTask(instrument)) { tasks.withType(Test) { doFirst { systemProperty 'jacoco-agent.destfile', buildDir.path + '/jacoco/testDebugCoverageUnitTest.exec' classpath = files(offline_instrumented_outputDir) + classpath + configurations.jacocoRuntime } } } } /* * Instruments the classes per se */ task instrument(dependsOn:'compileDebugUnitTestSources') { doLast { println 'Instrumenting classes' ant.taskdef(name: 'instrument', classname: 'org.jacoco.ant.InstrumentTask', classpath: configurations.jacocoAnt.asPath) ant.instrument(destdir: offline_instrumented_outputDir) { fileset(dir: "$buildDir.path/intermediates/javac/debugCoverage") } } } -----jacoco-report.gradle--------------- jacoco-report.gradle 在同一个项目中的base模块可以正常生成jacoco单元测试覆盖率报告,但是在app模块无法生成,build文件夹中,都没有jacoco和 test-results文件夹。上述的jacoco-report.gradle和build.gradle是app模块的内容 请问应该如何解决app模块无法显示jacoco报告的问题i
最新发布
07-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值