架构师日记-聊聊开发必掌握的那些实践技能
构建语言生态的优势,弥补其存在短板,始终是编程语言的一个演进方向。
1999年,美国太空总署(NASA)的火星任务失败:在这次任务中,火星气候探测者号上的飞行系统软件使用公制单位牛顿计算推进器动力,而地面人员输入的方向校正量和推进器参数则使用英制单位磅力,导致探测器进入大气层的高度有误,最终瓦解碎裂。
2.1 关于命名
-
蛇形命名法(snake case):又叫下划线命名法,使用下划线,单词小写,比如:my_system; -
驼峰命名法(camel case):按照单词首字母区分大小写,又可细分为大驼峰命名法和小驼峰命名法,比如:MySystem,mySystem; -
匈牙利命名法(HN case):属性+类型+描述,比如:nLength,g_cch,hwnd; -
帕斯卡命名法(Pascal case):全部首字母大写,等同于大驼峰命名法,比如:MySystem; -
脊柱命名法(spinal case):使用中划线,比如:my-system; -
自由命名法(studly caps):大小写混杂,无简明规则,比如:mySYSTEM,MYSystem;
2.1.1 命名字典
2.1.2 命名实践
-
项目名全部小写; -
包名全部小写; -
类名首字母大写,其余组成词首字母依次大写; -
变量名,方法名首字母小写,如果名称由多个单词组成,除首字母外的每个单词首字母都要大写; -
常量名全部大写;
-
自带混淆功能的变量名:String zhrmghg = "极致缩写型"; -
没有意义的万能变量名:String a,b,c="爱谁谁型"; -
长串拼音变量名:String HuaBuHua = "考古型"; -
各种符号混用:String $my_first_name_ = "打死记不住型"; -
大小写,数字,缩写混乱:String waitRPCResponse1 = "极易出错型";
public <PropertyType> get<PropertyName>();public void set<PropertyName>(<PropertyType> p)
public boolean is<PropertyName>();public void set<PropertyName>(boolean p)
-
一类是同一个jar包出现了多个不同的版本。应用选择了错误的版本导致jvm加载不到需要的类或者加载了错误版本的类;(借助maven管理工具相对容易解决) -
另一类是不同的jar包出现了类路径相同的类,同样的类出现在不同的依赖jar里,由于jar加载的先后顺序导致了JVM加载了错误版本的类;(比较难以解决)
public class SkuKey implements Serializable {private Long stationNo;private Long skuId;// 省略get/set方法}
2.2 关于注释
2.2.1 好的注释
-
系统注释:通过README.md文件体现宏观的功能和架构实现; -
包注释:通过package-info文件体现模块职责边界,另外该文件也支持声明友好类,包常量以及为标注在包上的注解(Annotation)提供便利; -
类注释:主要体现功能职责,版本支持,作者归属,应用示例等相关信息; -
方法注释:关注入参,出参,异常处理声明,使用场景举例等相关内容; -
代码块和代码行注释:主要体现逻辑意图,闭坑警示,规划TODO,放大关注点等细节内容;
2.2.2 坏的注释
-
冗余式:如果一个函数,读者能够很容易的就读出来代码要表达的意思,注释就是多余的; -
错误式:如果注释地不清楚,甚至出现歧义,那还不如不写; -
签名式:类似“add by liuhuiqing 2023-08-05”这种注释,容易过期失效而且不太可信(不能保证所有人每次都采用这种方式注释),其功能完全可以由git代码管理工具来实现; -
长篇大论式:代码块里,夹杂了大篇幅的注释,不仅影响代码阅读,而且维护困难; -
非本地注释:注释应该在离代码实现最近的地方,比如:被调用的方法注释就由方法本身来维护,调用方无需对方法做详细的说明; -
注释掉的代码:无用的代码应该删除,而不是注释。历史记录交给git等代码管理工具来维护;
2.3 关于分层
2.3.1 系统分层
2.3.2 软件伸缩性
软件伸缩性指的是软件系统在面对负载压力时,能够保持原有性能并扩展以支持更多任务的能力。
伸缩性可以有两个方面,垂直伸缩性和水平伸缩性,垂直伸缩性是通过在同一个业务单元中增加资源来提高系统的吞吐量,比如增加服务器cpu的数量,增加服务器的内存等。水平伸缩性是通过增加多个业务单元资源,使得所有的业务单元逻辑上就像是一个单元一样。比如ejb分布式组件模型,微服务组件模型等都属于此种方式。
2.4 小结
3.1 类定义
3.1.1 常量定义
public enum Color {RED, GREEN, BLUE;}
public class MyClass {public static final int MAX_VALUE = 100;}
3.1.2 工具类
public abstract class ObjectHelper {public static boolean isEmpty(String str) {return str == null || str.length() == 0;}}
3.1.3 JavaBean
public class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}}
import lombok.Data;(chain = true)public class Person {private String name;private int age;}
3.1.4 不可变类
public final class String implements Serializable, Comparable<String>, CharSequence {}
java.lang.Stringjava.lang.Mathjava.lang.Booleanjava.lang.Characterjava.util.Datejava.sql.Datejava.lang.Systemjava.lang.ClassLoader
3.1.5 匿名内部类
直接作为参数传递给方法或构造函数;
用于实现某个接口或抽象类的匿名实例;
public class Example {public static void main(String[] args) {// 创建一个匿名内部类Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("Hello, World!");}};// 调用匿名内部类的方法runnable.run();}}
3.1.6 声明类
-
AutoCloseable:表示实现了该接口的类可以被自动关闭,通常用于资源管理。 -
Comparable:表示实现了该接口的类可以与其他实现了该接口的对象进行比较。 -
Callable:表示实现了该接口的类可以作为参数传递给线程池,并返回结果。 -
Cloneable:表示实现了该接口的类可以被克隆。 -
Enum:表示实现了该接口的类是一个枚举类型。 -
Iterable:表示实现了该接口的类可以迭代。 -
Runnable:表示实现了该接口的类可以作为线程运行。 -
Serializable:表示实现了该接口的类可以被序列化和反序列化。 -
interface:表示实现了该接口的类是一个接口,可以包含方法声明。 -
Annotation:表示实现了该接口的类是一个注解,可以用于元数据描述。
3.1.7 Record 类
/*** 关键定义的类是不可变类* 将所有成员变量通过参数的形式定义* 默认会生成全部参数的构造方法* @param name* @param age*/public record Person(String name, int age) {public Person{if(name == null){throw new IllegalArgumentException("提供紧凑的方式进行参数校验");}}/*** 定义的类中可以定义静态方法* @param name* @return*/public static Person of(String name) {return new Person(name, 18);}}
Person person = new Person("John", 30);// Person person = Person.of("John");String name = person.name();int age = person.age();
public List<Person> sortPeopleByAge(List<Person> people) {record Data(Person person, int age){};return people.stream().map(person -> new Data(person, computAge(person))).sorted((d1, d2) -> Integer.compare(d2.age(), d1.age())).map(Data::person).collect(toList());}public int computAge(Person person) {return person.age() - 1;}
3.1.8 密封类
-
final修饰类,这样类就无法被继承了; -
package-private类,可以控制只能被同一个包下的类继承;
sealed class SealedClass permits SubClass1, SubClass2 {}class SubClass1 extends SealedClass {}class SubClass2 extends SealedClass {}
3.2 方法定义
3.2.1 构造方法
public class MyClass {private int myInt;private String myString;// 构造方法public MyClass(int myInt, String myString) {this.myInt = myInt;this.myString = myString;}}
3.2.2 方法重写
class Animal {public void makeSound() {System.out.println("Animal is making a sound");}}class Cat extends Animal {public void makeSound() {System.out.println("Meow");}}public class Main {public static void main(String[] args) {Animal myCat = new Cat();myCat.makeSound(); // 输出 "Meow"}}
3.2.3 方法重载
public class Calculator {public int add(int a, int b) {return a + b;}public double add(double a, double b) {return a + b;}}public class Main {public static void main(String[] args) {Calculator calculator = new Calculator();int result1 = calculator.add(2, 3);double result2 = calculator.add(2.5, 3.5);System.out.println(result1); // 输出 5System.out.println(result2); // 输出 6.0}}
3.2.4 匿名方法
public static void main(String args[]) {List<String> names = Arrays.asList("hello", "world");// 使用 Lambda 表达式作为参数传递给 forEach 方法names.forEach((String name) -> System.out.println("Name: " + name));// 使用 Lambda 表达式作为独立表达式使用Predicate<String> nameLengthGreaterThan5 = (String name) -> name.length() > 5;boolean isLongName = nameLengthGreaterThan5.test("John");System.out.println("Is long name? " + isLongName);}
3.3 对象定义
3.3.1 单例对象
-
控制资源的使用:通过线程同步来控制资源的并发访问。 -
控制实例产生的数量:达到节约资源的目的。 -
作为通信媒介使用:也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。
public enum Singleton {INSTANCE;public void someMethod() {// ...其他代码...}}
3.3.2 不可变对象
-
将对象的状态存储在不可变对象中:String、Integer等就是内置的不可变对象类型; -
将对象的状态存储在final变量中:final变量一旦被赋值就不能被修改; -
将对象的所有属性都设为不可变对象:这样就可以确保整个对象都是不可变的;
Collections.unmodifiableList(new ArrayList<>());
3.3.3 元组对象
public class Pair<A,B> {public final A first;public final B second;public Pair(A a, B b) {this.first = a;this.second = b;}public A getFirst() {return first;}public B getSecond() {return second;}}
public class Triplet<A,B,C> extends Pair<A,B>{public final C third;public Triplet(A a, B b, C c) {super(a, b);this.third = c;}public C getThird() {return third;}public static void main(String[] args) {// 表示姓名,性别,年龄Triplet<String,String,Integer> triplet = new Triplet("John","男",18);// 获得姓名String name = triplet.getFirst();}}
public class Tuple<E> {private final E[] elements;public Tuple(E... elements) {this.elements = elements;}public E get(int index) {return elements[index];}public int size() {return elements.length;}public static void main(String[] args) {// 表示姓名,性别,年龄Tuple<String> tuple = new Tuple<>("John", "男", "18");// 获得姓名String name = tuple.get(0);}}
-
存储多个数据元素:Tuple可以存储多个不同类型的数据元素,这些元素可以是基本类型、对象类型、数组等; -
简化代码:Tuple可以使代码更加简洁,减少重复代码的编写。通过Tuple,我们可以将多个变量打包成一个对象,从而减少了代码量; -
提高代码可读性:Tuple可以提高代码的可读性。通过Tuple,我们可以将多个变量打包成一个对象,从而使代码更加易读; -
支持函数返回多个值:Tuple可以支持函数返回多个值。在Java中,函数只能返回一个值,但是通过Tuple,我们可以将多个值打包成一个对象返回;
NamedTuple namedTuple = Tuples.named("person", "name", "age");
3.3.4 临时对象
-
尽量重用对象。由于系统不仅要花时间生成对象,以后可能还需花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响,重用对象的策略有缓存对象,也可以针对具体场景进行定向优化,比如使用StringBuffer代替字符串拼接的方式; -
尽量使用局部变量。调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快。其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢; -
分代收集。分代垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率;
3.3.5 Valhalla
性能增强通过展平对象图和移除间接来解决。这将获得更高效的内存布局和更少的分配和垃圾回收。
当用作泛型类型时,原语和对象具有更相似的行为,这是更好的抽象。
评论
