其实以前在zte,当时团队一堆人再争着看CC,自己并没有当回事儿,因为当时觉得,还是先把技术功底打好,多学多写代码。然而,自己忽视了代码的本质。没错,代码的本质是可阅读性。还好,不晚
零、一个简单的范例
// 求二叉树的深度
// 递归解法:
// (1)如果二叉树为空,二叉树的深度为0
// (2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
public int depth(node node) {
if (node == null) // 递归出口
return 0;
int l = depth(node.lchild);
int r = depth(node.rchild);
return l > r ? (l + 1) : (r + 1);
};
思考:我们有什么不对?
一、起始的一些思想
1、代码是根,无法丢弃
2、烂代码毁灭一切
3、勒布朗法则:稍后等于永不()
4、要保持专业精神
5、读与写的比例:10:1 ()
6、“让营地比你来时更干净”
7、六大原则:SRP、OCP、DIP、LOD、LSP、ISP、
二、命名
public List getThem(){
List list1 = new ArrayList<>();
for(int i = 0; i
思考:这会影响什么?
1、准确的命名
2、最好不要用List的后缀(或者是平台的专用词,工具类名)
3、getAccount()与getAccountInfo()的问题
4、可读性、课所搜性
5、Java无需要在名称中记住变量类型
6、尽量不要用i,j,k这些个变量进行循环计数
7、类名应该为名词或者是名词短语(Account)
方法名应该为动词或者是动词短语
8、add(a、增加新东西;b、连接现有的两个词),这种一语双关的命名就不是很好
@Data
class PersonInfo{
private String firstName;
private String lastName;
private String phone;
private String street;
private String city;
private String state;
}
public class UserHandle{
......
public void handle(PersonInfo p){
......
String s = p.getState();
......
}
......
}
思考:getState是什么Gui?
9、添加有意义的语境,例如,再firstName、street、city、state这样的一组词汇里面,大概知道我们 要表达的是地址,可是单独吧state这个单词拿出来,并不知道我们是要说的地址相关
三、函数
public Money calculatePay(Employee e) throws Exception{
switch(e.type){
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calsulateHourlyPay(e);
default:
throw new InvalidEmployee(e.type);
}
}
思考:这个违反了什么原则?
1、尽量短小
2、只做一件事情(同一抽象层级上面)
3、switch尽量在较低的抽象层级,且永远不重复(向下规则:从上面向下面读代码)
private PlainResult updateStockWhenTakeupProduct(GoodsOperateParam goodsOperateParam) {
......
//对多个门店上架时候设置库存
logger.info("stock setting:kdtId:{};deptId:{};goodsId:{}", kdtId, deptId, goodsId);
List stockList = stockMap.get(deptId);
//更新每个商品的sku库存
List goodsItemSkuDOS = getGoodsItemSkuDOS(kdtId, deptId, goodsId, stockList);//1
logger.info("goods item sku database param:{}", JsonUtils.toJson(goodsItemSkuDOS));
goodsItemSkuDAO.updateBatch(kdtId, deptId, goodsId, goodsItemSkuDOS);
//这里要设置商品总库存,就是设置分店中商品每个goodsId对应的库存总量,这个库存是对应每个sku库存相加所得
List sumStock = getSumStock(goodsId, stockList);//2
logger.info("dept service item database param:{}", JsonUtils.toJson(sumStock));
deptServiceItemDAO.batchUpdateDisplayStatusAndStock(kdtId, deptId, goodsOperateParam.getShelveType(), sumStock);
synESDeptStock(deptId, sumStock, goodsId, goodsOperateParam);//3
......
}
private List getGoodsItemSkuDOS(Long kdtId, Long deptId, Long goodsId, List stockList) {
List result = new ArrayList<>();
stockList.forEach(skuStock -> {
GoodsItemSkuDO goodsItemSkuDO = new GoodsItemSkuDO();
goodsItemSkuDO.setDeptId(deptId);
goodsItemSkuDO.setKdtId(kdtId);
goodsItemSkuDO.setGoodsId(goodsId);
goodsItemSkuDO.setSkuId(skuStock.getSkuId());
goodsItemSkuDO.setTotalStock(skuStock.getStockNum().intValue());
result.add(goodsItemSkuDO);
});
return result;
}
private List getSumStock(Long goodsId, List goodsStockParamList) {
List sumStock = new ArrayList<>();
Long sum = goodsStockParamList.stream().map(GoodsOperateParam.DeptStockParam::getStockNum).reduce(0L, Long::sum);
GoodsQuery.GoodsIdStock goodsIdStock = new GoodsQuery.GoodsIdStock(goodsId, sum);
sumStock.add(goodsIdStock);
return sumStock;
}
private void synESDeptStock(Long deptId, List sumStock, Long goodsId, GoodsOperateParam goodsOperateParam) {
EsGoodsDTO esGoodsDTO = new EsGoodsDTO();
esGoodsDTO.setDeptId(deptId);
esGoodsDTO.setGoodsId(goodsId);
esGoodsDTO.setOnShelve(goodsOperateParam.getShelveType());
esGoodsDTO.setStockNum(sumStock.get(0).getStockNum()); //@author jicheng 这里面细节进行统计每一个sku的库存累加,所以不用乘以所有sku的size了
if (esGoodsDTO.getStockNum() > 0) {
esGoodsDTO.setSoldStatus(GoodsStatusEnum.ON_SHELVE.getValue());
}
esGoodsManager.update(esGoodsDTO);
}
思考:还有什么改进点?请指教
4、尽量少入参
5、无副作用(不能即检查密码的正确性,又初始化系统)
6、避免使用输出参数
7、使用异常代替返回错误码
8、把trycatch主题抽出成为一个函数
9、代码尽量不要重复
10、一步步来改进代码,好代码不是一次就能成就的
......
//重复了 暂时还没考虑抽查来
if (plainResult.isSuccess()) {
PlainResult updateResult = null;
if (inputServingItemParam.getDeptId() == MultiShopType.HEADQUARTERS.deptId()) {
updateResult = goodsManager.updateServingItemV2(inputServingItemParam, serviceItemDOResult.getData());
}else {
updateResult = deptServiceItemManager.updateDept(inputServingItemParam);
}
plainResult.setCode(updateResult.getCode());
plainResult.setMessage(updateResult.getMessage());
plainResult.setSuccess(updateResult.isSuccess());
plainResult.getData().setId(updateResult.getData());
}
if (plainResult.isSuccess()) {
PlainResult updateDBResult = null;
if (inputServingItemParam.getDeptId() == MultiShopType.HEADQUARTERS.deptId()) {
updateDBResult = goodsManager.updateHeadGoodsInDB(inputServingItemParam);
}else {
updateDBResult = goodsManager.updateDeptGoodsInDB(inputServingItemParam);
}
if (!updateDBResult.isSuccess()) {
logger.warn("update service error,param={}", JsonUtils.toJson(inputServingItemParam));
}
}
......
思考:前人种树后人乘凉?
四、注释
// find out if gift already given
boolean isAlreadyGiven = false;
for (String given : giftsGiven) { //this is a loop
if(gift.equals(given)){
isAlreadyGiven = true;
break;
}
}
private boolean isGiftNotAlreadyGiven(String gift) {
boolean isAlreadyGiven = true;
for (String given : giftsGiven) {
if(gift.equals(given)){
isAlreadyGiven = false;
break;
}
}
return isAlreadyGiven;
}
思考:什么情况下我们需要注释?
1、不要因为代码乱而写注释,首先要改好老代码!
2、最好用代码的优秀的命名来取代注释
3、使用注释的场景罗列:法律信息、提供信息注释、对意图的解释或是阐释、警示作用、TODO注释
4、不好的注释:可怕的废话、类方法上的Javadoc注释、注释掉的代码、HTML注释、很多注释(信息过多 )、位置标记、显而易见的说明
/**
* Created by zhaoyifei.
*
* Time 2017/3/2 01:23
*
* Desc Goods对外的接口
*/
public interface GoodsService {
......
/**
* 总店 商品上下架, 支持批量, 以这个接口为基础,要拓展两个接口
* @update jicheng
* @author hunterPan
*/
PlainResult batchOperateByItemId(GoodsOperateParam goodsOperateParam);
......
}
思考:以上的接口注释好吗?
五、格式
1、短文件比长文件易于理解
2、垂直方向上的间隔(主要表现在方法之间、属性之间、还有逻辑之间)
3、少用protected,因为使用到的变量和方法尽量要靠近
4、调用与被调用的函数尽量要靠近
5、水平宽度建议:120字符(刚好一瓶)
6、遵守团队规则
六、对象与数据结构
1、解释两个名词:
- 对象:将数据隐藏于抽象之后,暴露操作数据的函数。
- 数据结构:暴露数据本身,不提供有意义的函数。
/*
Law of Demeter
定义:一个对象应该对其他对象保持最少的了解。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方案:尽量降低类与类之间的耦合。
解释:德米忒耳定律有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对
象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的
方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的
朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在
类的内部。
*/
//违反的案例
class CompanyManager{
public List getAllEmployee(){
List list = new ArrayList();
for(int i=0; i<30; i++){
Employee emp = new Employee();
//为总公司人员按顺序分配一个ID
emp.setId("总公司"+i);
list.add(emp);
}
return list;
}
public void printAllEmployee(SubCompanyManager sub){
List list1 = sub.getAllEmployee();
for(SubEmployee e:list1){
System.out.println(e.getId());
}
List list2 = this.getAllEmployee();
for(Employee e:list2){
System.out.println(e.getId());
}
}
}
//符合的案例
class CompanyManager{
public List getAllEmployee(){
List list = new ArrayList();
for(int i=0; i<30; i++){
Employee emp = new Employee();
//为总公司人员按顺序分配一个ID
emp.setId("总公司"+i);
list.add(emp);
}
return list;
}
public void printAllEmployee(SubCompanyManager sub){
sub.printEmployee();
List list2 = this.getAllEmployee();
for(Employee e:list2){
System.out.println(e.getId());
}
}
}
思考:返回值“好吗”?
2、Demeter定律(德米忒耳定律):类C的方法f,应该调用一下对象方法:(*)
- C
- 由f创建的对象
- 作为参数传递给f的对象
- 由C的实体变量持有的对象
final String filedir = ctct.getOptions().getScratchDir().getAbsolutePath();
/*
如果上面的ctct是一个对象就会违反Demeter定律,如果是一个数据结构就不会违反
属性访问就不会涉及到Demeter定律的问题
*/
七、错误处理
1、返回错误码的方式不是很好、尽量抛异常
2、尽量使用不可控异常(运行时异常),因为如果函数改成可控异常,那么调用者都要修改,必然会违反OCP
3、将较多的异常类型捕获,放到脚底的抽象层次里面
4、以下的代码有点不好,不要在异常处理块中做业务逻辑
try{
MealExpenses ex = experseReportDao.getMeals(employee.getId());
mTotal += ex.getTotal();
}catch(Exception e){
mTotal += getMealPerDiem();
}
6、不要返回null,并且不要传递null
八、边界
//前世
Map sensors = new HashMap();
dealSensors(sensors);
//今生
Sensors sensors = new Sensors();
dealSensors(sensors);
class Sensors{
private Map sensorContainer = new HashMap<>();
public Sensor getSensor(Long id){
return sensorContainer.get(id);
}
}
思考:什么是较低的抽象?
1、尽量不要将公共API当参数进行传递,有节制的进行封装
2、学习性测试(第三方的库)
九、单测
1、TDD流程
- 先不写生产代码
- 编写刚好不通过的单测
- 编写使得单测刚好通过的生产代码
2、测试代码与生产代码同等重要
3、测试代码非常关键的一点在于:可读性
4、每个测试尽量使用一个断言
5、FIRST(快速、独立、可重复、自足验证、及时)
十、类
public class Stack{
private int topOfStack = 0;
List elements = new LinkedList<>();
public int size(){
return topOfStack;
}
public void push(int element){
topOfStack++;
elements.add(element);
}
public int pop() throws PopedWhenEmpty{
if(topOfStack == 0){
throw new PopedWhenEmpty();
}
int element = elements.get(--topOfStack);
elements.remove(topOfStack);
return element;
}
}
思考:以上代码满足了什么原则?
1、类应该短小,权责尽量的少
2、内聚、SRP、OCP
3、大函数拆分成小函数,往往是拆分为多个小类的时机
十一、系统
1、构造与使用分开(DIP、工厂)
2、关注切面(AOP)
3、消除紧耦合
十二、逐步改进
1、自上而下重要点
- 运行所有的测试
- 不可重复
- 表达程序员的意图
- 尽可能减少类和方法的数量
十三、一些其余的点
enum CheckType{
INTEGER(0, "integer"),
STRING(1, "string");
private int value;
private String name;
}
interface Checker{
void check();
}
class IntegerChecker implements Checker{
public void check(){}
}
class StringChecker implements Checker{
public void check(){}
}
public CheckerHandler{
private static Map checkers = new HashMap(){{
put(CheckType.INTEGER.getValue(),(Checker)(new IntegerChecker()));
put(CheckType.STRING.getValue(),(Checker)(new StringChecker()));
}};
public void beginToCheck(){
for(CheckType checkerType : CheckType.values()){
Checker checker = checkers.get(checkerType.getValue());
checker.check();
}
}
}
思考:还有什么方式取代if/else,switch/case
1、用改对象的状态来代替输出参数
2、干掉死函数
3、用多态代替ifelse与switch
4、用命名常量代替魔法数字
5、避免用否定条件
6、掩蔽时序性耦合
7、函数应该只在一个抽象层级上面
8、较高层级放可配置的数据,用参数向下传递
9、使用枚举