通用编程_通用编程准则

通用编程

本文是我们名为“ 高级Java ”的学院课程的一部分。

本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程! 在这里查看

1.简介

在本部分的教程中,我们将继续讨论Java良好编程风格和健壮设计的一般原理。 我们已经在本教程的前面部分中看到了其中一些原则,但是在此过程中将引入许多新的实用建议,以提高您作为Java开发人员的技能。

2.可变范围

在本教程的第3部分“ 如何设计类和接口”中 ,我们讨论了如何将可见性和可访问性应用于类和接口成员,从而限制了它们的范围。 但是,我们尚未讨论在方法实现中使用的局部变量。

在Java语言中,每个局部变量(一旦声明)都有一个作用域。 从声明位置到声明的方法(或代码块)的末尾,该变量变得可见。因此,只有一条规则可遵循:将局部变量声明为靠近其所在的位置尽可能使用。 让我们看一些典型的例子:

for( final Locale locale: Locale.getAvailableLocales() ) {
    // Some implementation here
}

try( final InputStream in = new FileInputStream( "file.txt" ) ) {
    // Some implementation here
}

在这两个代码段中,局部变量的范围都限于它们在其中声明的执行块。一旦块结束,局部变量将超出范围,并且不再可见。 看起来简洁明了,但是随着Java 8的发布和lambda的引入,使用局部变量的许多众所周知的习惯用法已经过时了。 让我们重写前面示例中的for-each循环,改为使用lambda:

Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> {
        // Some implementation here
    }
);

局部变量成为函数的参数,该函数本身作为forEach方法的参数传递。

3.类字段和局部变量

Java中的每个方法都属于某个类(如果是Java 8,则将该接口声明为default ,则属于某个接口)。 这样,在方法实现中使用的局部变量与类成员之间就有名称冲突的可能性。 Java编译器可以从范围中选择正确的变量,尽管它可能不是打算使用的开发者。 为了向开发人员提示发生此类冲突(警告,突出显示等)的时间,现代Java IDE进行了大量工作,但在开发时最好考虑一下。 让我们看一下这个例子:

public class LocalVariableAndClassMember {
     private long value;

     public long calculateValue( final long initial ) {
         long value = initial;        

         value *= 10;
         value += value;

         return value;
     }
 }

该示例看起来很简单,但是有一个陷阱。 方法calculateValue引入一个具有名称value的局部变量,并以此隐藏具有相同名称的类成员。 第08行应该将类成员和局部变量相加,但是它所做的却大不相同。 正确的版本可能如下所示(使用关键字this ):

public class LocalVariableAndClassMember {
     private long value;

     public long calculateValue( final long initial ) {
         long value = initial;        

         value *= 10;
         value += this.value;        

         return value;
     }
 }

天真的实现,但它突出了重要的问题,在某些情况下,调试和故障排除可能需要数小时。

4.方法参数和局部变量

Java开发人员经常遇到的另一个陷阱是使用方法参数作为局部变量。 Java允许使用不同的值重新分配非final方法参数(但是,它对原始值没有任何影响)。 例如:

public String sanitize( String str ) {
    if( !str.isEmpty() ) {
        str = str.trim();
    }

    str = str.toLowerCase();
    return str;
}

它不是一段漂亮的代码,但足以说明问题:将方法参数str重新分配给另一个值(基本上用作局部变量)。 在所有情况下(无一例外),可以并且应该避免这种模式(例如,通过将方法参数声明为final )。 例如:

public String sanitize( final String str ) {
    String sanitized = str;

    if( !str.isEmpty() ) {
        sanitized = str.trim();
    }

    sanitized = sanitized.toLowerCase();
    return sanitized;
}

即使遵循引入局部变量的代价,遵循此简单规则的代码也更易于遵循和推理。

5.装箱和拆箱

装箱和拆箱都是Java语言中用于在原始类型(如intlongdouble )之间转换为各自的原始类型包装程序(如IntegerLongDouble )的相同技术的名称。 在本教程的第4部分“ 如何以及何时使用泛型”中 ,我们已经在将原始类型包装器作为泛型类型参数讨论时看到了它的作用。

尽管Java编译器试图通过执行自动装箱来尽力隐藏这些转换,但有时它会使情况变得更糟并导致意外的结果。 让我们看一下这个例子:

public static void calculate( final long value ) {
    // Some implementation here
}
final Long value = null;
 calculate( value );

上面的代码段可以很好地编译,但是当Longlong之间的转换发生时,它将在第02行抛出NullPointerException 。 这里的建议是更喜欢使用原始类型(但是,正如我们已经知道的那样,并非总是可能的)。

6.接口

在本教程的第3部分“ 如何设计类和接口”中 ,我们讨论了基于接口和基于契约的开发,并着重强调了这样的事实,即接口应尽可能优先于具体类。 本节的目的是通过展示真实的示例来说服您再有时间首先考虑接口。

接口不依赖于任何特定的实现(默认方法是一个例外)。 它们只是合同,因此在履行合同的方式上提供了很多自由和灵活性。 当实施涉及外部系统或服务时,这种灵活性变得越来越重要。 让我们看一下以下简单接口及其可能的实现:

public interface TimezoneService {
    TimeZone getTimeZone( final double lat, final double lon ) throws IOException;
}
public class TimezoneServiceImpl implements TimezoneService {
    @Override
    public TimeZone getTimeZone(final double lat, final double lon) throws IOException {
        final URL url = new URL( String.format(
            "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo",
            lat, lon
        ) );
        final HttpURLConnection connection = ( HttpURLConnection )url.openConnection();

        connection.setRequestMethod( "GET" );
        connection.setConnectTimeout( 1000 );
        connection.setReadTimeout( 1000 );
        connection.connect();

        int status = connection.getResponseCode();
        if (status == 200) {
            // Do something here
        }

        return TimeZone.getDefault();
    }
}

上面的代码段演示了典型的接口/实现模式。 该实现使用外部HTTP服务( http://api.geonames.org/ )来检索特定位置的时区。 但是,由于联系方式是由界面驱动的,因此很容易引入另一种使用数据库或什至平面文件的实现。 这样,接口可以极大地帮助设计可测试的代码。 例如,在每次测试运行时调用外部服务并不总是可行的,因此提供替代的虚拟实现(也称为存根或模拟)是有意义的:

public class TimezoneServiceTestImpl implements TimezoneService {
    @Override
    public TimeZone getTimeZone(final double lat, final double lon) throws IOException {
        return TimeZone.getDefault();
    }
}

此实现可在需要TimezoneService接口的每个地方使用,从而将测试方案与对外部组件的依赖隔离开来。

在Java标准集合库中封装了许多适当使用接口的出色示例。 CollectionListSet ,所有这些接口都有几种实现的支持,当倾向于使用合同时,这些实现可以无缝且可互换地替换,例如:

public static< T > void print( final Collection< T > collection ) {
    for( final T element: collection ) {
        System.out.println( element );
    }
}
print( new HashSet< Object >( /* ... */ ) );
print( new ArrayList< Integer >( /* ... */ ) );
print( new TreeSet< String >( /* ... */ ) );
print( new Vector< Long >( /* ... */ ) );

7.琴弦

字符串是Java以及大多数编程语言中使用最广泛的类型之一。 Java语言通过本机支持串联和比较,大大简化了字符串常规操作。 另外,Java标准库提供了许多不同的类来提高字符串操作的效率,这就是我们将在本节中讨论的内容。

在Java中,字符串是不可变的对象,以UTF-16格式表示。 每次连接字符串(或执行任何修改原始字符串的操作)时,都会创建String类的新实例。 因此,连接操作可能会变得非常无效,从而导致创建许多中间字符串实例(通常来说,会生成垃圾)。

但是Java标准库提供了两个非常有用的类,旨在促进字符串操作: StringBuilderStringBuffer (它们之间的唯一区别是StringBuffer是线程安全的,而StringBuilder不是)。 让我们看看使用这些类之一的几个示例:

final StringBuilder sb = new StringBuilder();

for( int i = 1; i <= 10; ++i ) {
    sb.append( " " );
    sb.append( i );
}

sb.deleteCharAt( 0 );
sb.insert( 0, "[" );
sb.replace( sb.length() - 3, sb.length(), "]" );

尽管建议使用StringBuilder / StringBuffer来操作字符串,但是在连接两个或三个字符串的简单情况下,它看起来可能会过分杀伤,因此可以使用常规+运算符代替,例如:

String userId = "user:" + new Random().nextInt( 100 );

通常,直接连接的更好替代方法是使用字符串格式设置,并且Java标准库也通过提供静态帮助器方法String.format来提供帮助。 它支持一组丰富的格式说明符,包括数字,字符,日期/时间等(有关完整参考,请访问官方文档 )。 让我们通过示例来探索格式化的力量:

String.format( "%04d", 1 );                      -> 0001
String.format( "%.2f", 12.324234d );             -> 12.32
String.format( "%tR", new Date() );              -> 21:11
String.format( "%tF", new Date() );              -> 2014-11-11
String.format( "%d%%", 12 );                     -> 12%

String.format方法提供了一种干净方便的方法来从不同的数据类型构造字符串。 值得一提的是,某些现代Java IDE能够根据传递给String.format方法的参数来分析格式规范,并在检测到任何不匹配时向开发人员发出警告。

8.命名约定

Java作为一种语言并不能强迫开发人员严格遵守任何命名约定,但是社区已经开发了一套易于遵循的规则,这些规则使Java代码在标准库和其他任何Java项目中看起来都统一。

  • 软件包名称小写形式输入: org.junitcom.fasterxml.jacksonjavax.json
  • 类,枚举,接口注释名称大写字母键入: StringBuilderRunnable@Override
  • 方法字段名static final除外)以驼峰形式输入: isEmptyformataddAll
  • 静态最终字段枚举常量名称大写形式键入, 并用下划线 “ _” MIN_RADIXLOGMIN_RADIXINSTANCE
  • 局部变量方法参数名称驼峰式键入: strnewLengthminimumCapacity
  • 泛型类型参数名称通常以大写形式表示为一个字符TUE

通过遵循这些简单的约定,您正在编写的代码将与其他任何库或框架看起来简洁明了,并没有区别,给人的印象是它是由同一人创作的(约定真正起作用的罕见情况之一)。

9.标准库

无论您从事哪种Java项目,Java标准库都是您最好的朋友。 是的,很难不同意它们有一些粗糙的边缘和奇怪的设计决策,但是在99%的情况下,这是专家编写的高质量代码。 值得学习。

每个Java版本都为现有库带来了许多新功能(可能会淘汰旧功能),并增加了许多新库。 Java 5带来了在java.util.concurrent包下协调的新并发库。 Java 6提供了(很少为人所知)脚本支持( javax.script包)和Java编译器API(在javax.tools包下)。 Java 7对java.util.concurrent了很多改进,在java.nio.file包下引入了新的I / O库,并通过java.lang.invoke包支持了动态语言。 最后,Java 8提供了一个期待已久的日期/时间API,该API在java.time程序包下java.time

Java作为平台正在不断发展,紧跟这一发展非常重要。 每当您打算将第三方库或框架引入您的项目时,请确保Java标准库中没有所需的功能(实际上,有许多专门的高性能算法实现要优于标准库中的实现)但在大多数情况下,您实际上并不需要它们)。

10.不变性

不变性遍及本教程,在这一部分中,它提醒您:请认真对待不变性。 如果您正在设计的类或正在实现的方法可以提供不变性保证,那么它几乎可以在任何地方使用,而不必担心会被并发修改。 这将使您作为开发人员的生活更加轻松(并希望您的队友的生活也更加轻松)。

11.测试

测试驱动开发(TDD)实践在Java社区中非常流行,这提高了所编写代码的质量标准。 与所有这些考虑TDD在桌子上带来的好处,这是可悲的,观察到Java标准库不包含任何测试框架或棚架今天的。

尽管如此,测试已成为现代Java开发中必不可少的部分,在本节中,我们将介绍使用出色的JUnit框架的一些基础知识。 本质上,在JUnit中 ,每个测试都是关于预期对象状态或行为的一组断言。

编写出色的测试的秘诀在于使它们简短而简单,一次只测试一件事。 作为练习,让我们编写一组测试来验证“ 字符串 ”部分中的String.format函数是否返回了所需的结果。

package com.javacodegeeks.advanced.generic;

import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.equalTo;

import org.junit.Test;

public class StringFormatTestCase {
    @Test
    public void testNumberFormattingWithLeadingZeros() {
        final String formatted = String.format( "%04d", 1 );
        assertThat( formatted, equalTo( "0001" ) );
    }

    @Test
    public void testDoubleFormattingWithTwoDecimalPoints() {
        final String formatted = String.format( "%.2f", 12.324234d );
        assertThat( formatted, equalTo( "12.32" ) );
    }
}

测试看起来非常可读,其执行是实例化。 如今,普通的Java项目包含数百个测试用例,为开发人员提供了有关正在开发的回归或功能的快速反馈。

12.接下来是什么

本教程的这一部分完成了与Java编程实践和指南相关的一系列讨论。 在下一部分中,我们将通过探索Java异常的世界,它们的类型,使用方式和使用时间来返回到语言功能。

13.下载源代码

这是关于“通用编程准则”的课程,是“高级Java”课程的课程。 您可以在此处下载源代码: AdvancedJavaPart7

翻译自: https://www.javacodegeeks.com/2015/09/general-programming-guidelines.html

通用编程

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 华为C通用编程规范是为了提高华为公司内部C语言编程的规范化程度而制定的一系列规范。这些规范主要包括命名规范、缩进和格式规范、注释规范、代码复用规范、错误处理规范等。 首先,命名规范要求使用有意义的变量名、函数名和常量名,避免使用缩写或者过于简单的命名。此外,命名规范还要求符合统一的命名风格,比如驼峰命名法或下划线命名法。 其次,缩进和格式规范要求代码的缩进需要统一,一般为4个空格或者一个Tab键。同时,代码的格式要整齐,包括合理的函数和语句间隔、大括号的使用等。 注释规范要求对代码进行充分注释,包括函数的作用、参数的说明、返回值的说明等。注释的书写应该清晰明了,方便其他人理解代码的功能。 代码复用规范要求优先使用已经封装好的函数或者模块,避免重复编写相似的代码,提高代码的可重用性和维护性。 最后,错误处理规范要求对于可能出现的错误情况,需要进行适当的错误处理。在代码中加入错误处理的相关代码,比如判断返回值、处理异常等,以保证代码的健壮性和稳定性。 通过遵守华为C通用编程规范,可以提高代码的可读性、可维护性和可重用性,减少程序出错的可能性,从而提高华为公司内部C语言编程的质量和效率。 ### 回答2: 华为C通用编程规范是一套适用于华为公司C语言开发的规范和准则。它旨在保证软件开发的质量、可维护性和可扩展性,提高代码的可读性和可靠性。 首先,华为C通用编程规范要求在编码时遵循一致的命名规则。变量、函数、宏等的命名要准确、具有可读性,并避免使用缩写和无意义的命名。同时,对于具有特定作用域的变量,要求在其作用域结束后立即释放。 其次,规范还要求使用注释来解释代码的功能和实现细节。注释应该清晰明了,帮助其他开发人员理解代码的意图和思路。此外,规范还对注释的格式和位置进行了详细规定。 另外,华为C通用编程规范还对代码结构做出了要求。它鼓励使用模块化的方式组织代码,将功能相似的代码放在同一个模块中,提高代码的可重用性和可维护性。同时,规范还对代码缩进、空行、对齐等进行了规定,以确保代码的可读性。 此外,规范还强调了安全性和可靠性的考虑。它要求在编码过程中注意边界条件的检查,避免内存泄漏和缓冲区溢出等常见的安全问题。 总的来说,华为C通用编程规范是一套准则和规范,旨在提高华为公司C语言开发的质量和效率。通过规范的遵循,可以使代码更易于理解和维护,提高软件开发的质量和可靠性。 ### 回答3: 华为C通用编程规范是为了在华为C平台上编写高质量、高效率的代码而制定的一系列规范和指南。下面是一些主要的规范内容: 1. 命名规范:变量、函数、常量、宏等命名应具有一定的描述性,尽量简洁明了。遵循驼峰命名法,并采用统一的命名风格。 2. 注释规范:在代码中添加适当的注释,用于解释代码的用途、实现方式等。注释应该清晰明了,避免使用过于简单或晦涩的注释。 3. 缩进和排版规范:代码应该采用统一的缩进风格,以提高可读性。使用适当的空格和换行来使代码结构清晰明了。 4. 函数规范:函数应该具有明确的功能和单一的责任,参数的数量应控制在合理范围内。尽量避免使用全局变量,提倡使用局部变量。 5. 错误处理规范:代码应该在可能出现错误的地方进行适当的错误处理,包括返回错误码、抛出异常等。同时,要注重错误信息的准确和可读性。 6. 内存管理规范:合理使用内存,尽量减少内存泄漏和内存溢出的可能。在不使用的内存对象上进行垃圾回收和释放。 7. 并发和多线程规范:在多线程环境下,要注意线程安全和竞态条件的问题,采用适当的同步机制来确保数据的一致性。 8. 异常处理规范:合理使用异常处理机制,捕获和处理可能出现的异常,保证程序的稳定性和健壮性。 通过遵守华为C通用编程规范,可以使开发者编写出更加规范、易读、易维护的代码,提高开发效率,降低代码的bug率,提升软件的质量。此外,使用统一的编程规范还方便团队合作,提高代码的可维护性和可扩展性,有利于项目的整体进展。通过培养和强调良好的编程规范,可以更好地保障软件开发过程中的质量和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值