普通的工程師堆砌代碼,優秀的工程師優雅代碼,卓越的工程師簡化代碼。如何寫出優雅整潔易懂的代碼是一門學問,也是軟件工程實踐裏重要的一環。--來自網絡java
軟件質量,不但依賴於架構及項目管理,更與代碼質量緊密相關。簡潔高效的代碼不但易於閱讀,更能避免潛在BUG與風險,提升代碼質量。近期,一位Oracle程序員在Hacker News上吐槽本身的工做,引發了熱議。程序員
這個工程師的核心痛點是,Oracle經歷長期的產品線迭代,代碼異常龐大、邏輯複雜,整個代碼中充斥着神祕的宏命令。每新增一個特性或者修復BUG,該工程師都須要大量的調研,當心謹慎的進行着平常的工做。而Oracle每次的版本發佈都經歷數百萬次的測試,腦補一下,如噩夢通常。那麼咱們應該如何編寫簡潔高效的代碼呢?其實業內有過不少相關書籍,好比經典的書籍有《代碼整潔之道》、《編寫可讀代碼的藝術》、《重構:改善既有代碼的設計》,可用於修煉內功。以及咱們有嚴格的代碼規範以及方便的靜態代碼掃描工具,可用於增強研發代碼質量能力。算法
其實代碼規範和靜態代碼掃描工具可以幫助咱們完成不少代碼簡潔的工做。諸如:註釋、命名、方法、異常、單元測試等多個方面。但卻沒法總結了一些代碼簡潔最佳實踐,其實Java是面向對象語音,而面向對象的特徵是封裝、繼承、多態,巧妙的運用這三大特性、理解Java的一些關鍵字特性、語音特性、閱讀JDK源碼,就能夠寫出相對簡潔的代碼了。編程
// 修改前 if(list.size()>0) { return true; } else { return false; } // 修改後 return list.size()>0;
// 修改前 public List<Map<String, Object>> queryList(Map<String, Object> params) { List<Map<String, Object>> list = null; try { list = mapper.queryList(params); } catch (Throwable e) { throw new RuntimeException("ERROR", e); } return list; } // 修改後 public List<Map<String, Object>> queryList(Map<String, Object> params) { try { return mapper.queryList(params); } catch (Throwable e) { throw new RuntimeException("ERROR", e); } }
// 修改前 if (0 == retCode) { sendMessage("A001", "Process Success", outResult); } else { sendMessage("A001", "Process Failure", outResult); } // 修改後 1 String message = (0 == retCode ? "Process Success" : "Process Failure"); sendMessage("A001", message, outResult); // 修改後 2 sendMessage("A001", messageFromRetCode(retCode), outResult);
// 修改前 String uuid = UUIDUtils.getUUID(); String date = DateTimeUtils.getCurrDt(); String time = DateTimeUtils.getCurrTm(); Order order = new Order(); order.setSrUsrId(map.get("srcUsrId")); // 省略幾十個set ... order.setTmsDate(date); order.setTmsCte(time); order.setUuid(uuid); list.add(order); // 修改後 list.add(buildOrder(map)); public Order buildOrder(Map<String,String> map){ Order order = new Order(); order.setSrUsrId(map.get("srcUsrId")); // 省略幾十個set ... String date = DateTimeUtils.getCurrDt(); order.setTmsDate(date); order.setTmsCte(DateTimeUtils.getCurrTm()); String uuid = UUIDUtils.getUUID(); order.setUuid(uuid); return order; }
JAVA8特性「函數式編程」,使用Lambdas咱們能作到什麼?設計模式
// 修改前 public static void test1() { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); for (int number : numbers) { System.out.println(number); } } // 修改後1 // 使用lambda表達式以及函數操做(functional operation) public static void test2(){ List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); numbers.forEach((Integer value)-> System.out.println(value)); } // 修改後2 //在Java8中使用雙冒號操做符(double colon operator) public static void test3(){ List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); numbers.forEach(System.out::println); }
上述代碼是傳統方式的遍歷一個List的寫法,簡單來講主要有3個不足:數組
而使用函數式編程能規避上面的三個問題:安全
在Java8中,接口中的方法能夠被實現,用關鍵字 default 做爲修飾符來標識,接口中被實現的方法叫作 default 方法。使用default方法,當接口發生改變的時候,實現類不須要作改動,全部的子類都會繼承 default 方法。網絡
public class Test1 { public static void main(String[] args) { Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; System.out.println(formula.calculate(100)); // 100.0 System.out.println(formula.sqrt(16)); // 4.0 } } interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
當一個接口擴展另一個包含默認方法的接口的時候,有如下3種處理方式。多線程
Java8中新增了LocalDate和LocalTime接口,爲何要搞一套全新的處理日期和時間的API?由於舊的java.util.Date實在是太難用了。架構
固然,LocalDateTime才能同時包含日期和時間。
新接口更好用的緣由是考慮到了日期時間的操做,常常發生往前推或日後推幾天的狀況。用java.util.Date配合Calendar要寫好多代碼,並且通常的開發人員還不必定能寫對。
Clock c = Clock.systemDefaultZone(); System.out.println(System.currentTimeMillis()); System.out.println(c.millis()); Date date = Date.from(c.instant()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(date)); // instant精確到納秒,比原來的date的毫秒要更精確 // 獲取當前時間 Instant in = Instant.now(); System.out.println(in); // 將如今的時間增長3小時2分,將產生新的實例 Instant in1 = in.plus(Duration.ofHours(3).plusMinutes(2)); System.out.println(in1); System.out.println(in1 == in); // 關於計算的例子 in.minus(5, ChronoUnit.DAYS);// 計算5天前 in.minus(Duration.ofDays(5));// 計算5天前 // 計算兩個Instant之間的分鐘數 long diffAsMinutes1 = ChronoUnit.MINUTES.between(in, in1); // 方法2 System.out.println(diffAsMinutes1); // instant是可比較的,有isAfter和isBefore System.out.println(in.isAfter(in1)); System.out.println(in.isBefore(in1));
// 取當前日期 LocalDate today = LocalDate.now(); System.out.println(today); // 得到2005年的第86天 (27-Mar-2005) LocalDate localDate = LocalDate.ofYearDay(2005, 86); System.out.println(localDate); // 根據年月日取日期 2013年8月10日 localDate = LocalDate.of(2013, Month.AUGUST, 10); localDate = LocalDate.of(2013, 8, 10); // 根據字符串取 LocalDate.parse("2014-02-28"); // 嚴格按照ISO yyyy-MM-dd驗證,02寫成2都不行 LocalDate.parse("2014-02-29"); // 無效日期沒法經過:DateTimeParseException: Invalid date // 取本月第1天: LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 取本月第2天: LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 取本月最後一天,不再用計算是28,29,30仍是31: LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 取下一天: LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 變成了2015-01-01 // 取2015年1月第一個週一,這個計算用Calendar要死掉不少腦細胞: LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01") .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // LocalTime LocalTime now = LocalTime.now(); // 帶納秒 LocalTime now1 = LocalTime.now().withNano(0); // 清除納秒 System.out.println(now); System.out.println(now1); LocalTime localTime = LocalTime.of(22, 33); System.out.println(localTime); // 返回一天中的第4503秒 localTime = LocalTime.ofSecondOfDay(4503); System.out.println(localTime); LocalDateTime localDateTime0 = LocalDateTime.now(); System.out.println(localDateTime0); // 當前時間加上25小時3分鐘 LocalDateTime inTheFuture = localDateTime0.plusHours(25).plusMinutes(3); System.out.println(inTheFuture); // 一樣也能夠用在localTime和localDate中 System.out.println(localDateTime0.toLocalTime().plusHours(25).plusMinutes(3)); System.out.println(localDateTime0.toLocalDate().plusMonths(2));
Stream是對集合的包裝,一般和lambda一塊兒使用。使用lambdas能夠支持許多操做。如 map,filter,limit,sorted,count,min,max,sum,collect等等。 一樣,Stream使用懶運算,他們並不會真正地讀取全部數據。遇到像getFirst()這樣的方法就會結束鏈式語法,經過下面一系列例子介紹:好比我有個Person類,就是一個簡單的pojo, 針對這個對象,咱們可能有這樣一系列的運算需求。
class Person{ private String name, job, gender; private int salary, age; // 省略若干get/set方法及構造方法 ... }
List<Person> persons = new ArrayList<Person>() { private static final long serialVersionUID = 1L; { add(new Person("張三", "Java", "female", 25, 1000)); add(new Person("李四", "Java", "male", 29, 1200)); add(new Person("王五", "測試", "female", 25, 1400)); add(new Person("趙六", "Java", "male", 31, 1800)); add(new Person("張三三", "設計", "male", 33, 1900)); add(new Person("李四四", "需求", "female", 30, 2000)); add(new Person("王五五", "Java", "female", 29, 2100)); add(new Person("趙六六", "Java", "male", 43, 2800)); } };
Consumer<Person> print = e -> System.out.println(e.toString()); persons.forEach(print);
Consumer<Person> raise = e -> e.setSalary(e.getSalary()/100*10+e.getSalary()); persons.forEach(raise); persons.forEach(print);
persons.stream().filter((p) -> (p.getSalary()< 1500)).forEach(print);
Predicate<Person> salaryPredicate = e -> e.getSalary() > 2000; Predicate<Person> jobPredicate = e -> "Java".equals(e.getJob()); Predicate<Person> agePredicate = e -> e.getAge() >= 29; Predicate<Person> genderPredicate = e -> "female".equals(e.getGender()); persons.stream().filter(salaryPredicate) .filter(jobPredicate) .filter(agePredicate) .filter(genderPredicate) .forEach(print);
persons.stream().filter(genderPredicate).limit(2).forEach(print);
persons.stream().sorted((p1,p2)-> (p1.getAge() - p2.getAge())) //.sorted((p1,p2)->(p1.getName().compareTo(p2.getName()))) .forEach(print);
System.out.println(persons.stream().min((p1,p2)->(p1.getSalary()-p2.getSalary())).get().toString()); System.out.println(persons.stream().max((p1,p2)->(p1.getSalary()-p2.getSalary())).get().toString());
System.out.println("全部人的工資總和:"+ persons.stream().parallel().mapToInt(p - > p.getSalary()).sum());
String str = persons.stream().map(Person::getName).collect(Collectors.joining(";")); System.out.println(str); TreeSet<String> ts = persons.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new)); System.out.println("ts.toString():"+ts.toString()); Set<String> set = persons.stream().map(Person::getName).collect(Collectors.toSet()); System.out.println("set.toString():"+set.toString());
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IntSummaryStatistics stats = numbers .stream() .mapToInt((x) -> x) .summaryStatistics(); System.out.println("List中最大的數字 : " + stats.getMax()); System.out.println("List中最小的數字 : " + stats.getMin()); System.out.println("全部數字的總和 : " + stats.getSum()); System.out.println("全部數字的平均值 : " + stats.getAverage());
List<Integer> numbers1 = Arrays.asList(9, 10, 3, 4, 7, 3, 4); List<Integer> distinct = numbers1.stream().distinct().collect(Collectors.toList()); System.out.println(distinct);
//sumAll算法很簡單,完成的是將List中全部元素相加。 public static int sumAll(List<Integer> numbers) { int total = 0; for (int number : numbers) { total += number; } return total; }
sumAll算法很簡單,完成的是將List中全部元素相加。某一天若是咱們須要增長一個對List中全部偶數求和的方法sumAllEven,那麼就產生了sumAll2,以下:
public static int sumAll2(List<Integer> numbers) { int total = 0; for (int number : numbers) { if (number % 2 == 0) { total += number; } } return total; }
又有一天,咱們須要增長第三個方法:對List中全部大於3的元素求和,那是否是繼續加下面的方法呢?sumAll3
public static int sumAll3(List<Integer> numbers) { int total = 0; for (int number : numbers) { if (number > 3) { total += number; } } return total; }
觀察這三個方法咱們發現,有不少重複內容,惟一不一樣的是方法中的if條件不同(第一個能夠當作if(true)),若是讓咱們優化,可能想到的第一種重構就是策略模式吧,代碼以下:
public interface Strategy { public boolean test(int num); } public class SumAllStrategy implements Strategy { @Override public boolean test(int num) { return true; } } public class SumAllEvenStrategy implements Strategy { @Override public boolean test(int num) { return num % 2 == 0; } } public class SumAllGTThreeStrategy implements Strategy { @Override public boolean test(int num) { return num > 3; } } public class BodyClass { private Strategy stragegy = null; private final static Strategy DEFAULT_STRATEGY = new SumAllStrategy(); public BodyClass() { this(null); } public BodyClass(Strategy arg) { if (arg != null) { this.stragegy = arg; } else { this.stragegy = DEFAULT_STRATEGY; } } public int sumAll(List<Integer> numbers) { int total = 0; for (int number : numbers) { if (stragegy.test(number)) { total += number; } } return total; } } //調用 BodyClass bc = new BodyClass(); bc.sumAll(numbers);
這無疑使用設計模式的方式優化了冗餘代碼,可是可能要額外增長几個類,之後擴展也要新增,下面看看使用lambda如何實現,聲明方法:第一個參數仍是咱們以前傳遞的List數組,第二個看起來可能有點陌生,經過查看jdk能夠知道,這個類是一個謂詞(布爾值的函數)
public static int sumAllByPredicate(List<Integer> numbers, Predicate<Integer> p) { int total = 0; for (int number : numbers) { if (p.test(number)) { total += number; } } return total; } //調用: sumAllByPredicate(numbers, n -> true); sumAllByPredicate(numbers, n -> n % 2 == 0); sumAllByPredicate(numbers, n -> n > 3);
代碼是否是比上面簡潔了不少?語義也很明確,重要的是無論之後怎麼變,均可以一行代碼就修改了。。。萬金油啊。
JAVA8 還推出了不少特性,來簡化代碼。好比String.join函數、Objects類、Base64編碼類。
String joined = String.join("/", "usr","local","bin"); String joided1="usr/"+"local/"+"bin/"; System.out.println(joined); String ids = String.join(", ", ZoneId.getAvailableZoneIds()); System.out.println(ids);
String aa = null; Objects.requireNonNull(aa," aa must be not null"); Object a = null; Object b = new Object(); if(a.equals(b)){ } if(Objects.equals(a, b)){ }
Base64.Encoder encoder = Base64.getEncoder(); Base64.Decoder decoder = Base64.getDecoder(); String str = encoder.encodeToString("你好".getBytes(StandardCharsets.UTF_8)); System.out.println(str); System.out.println(new String(decoder.decode(str),StandardCharsets.UTF_8));
好的代碼須要不停的打磨,做爲一個優秀的工程師,咱們應該嚴格遵照,每次提交的代碼要比遷出的時候更好。常常有人說,做爲工程師必定要有團隊精神,但這種精神並非說說而已的,須要實際的行動來體現的。設計模式、JDK的新特性都是咱們能夠藉助的經驗,編碼完成後思考一下,還可不能夠在簡化、優化,不要成爲一個「做惡」的工程師。
馬鐵利,隨行付架構部負責人 & TGO鯤鵬會北京分會會員,10年全棧工程師,擅長微服務分佈式架構設計。主要負責隨行付架構部平常管理;參與構建微服務平臺周邊基礎設施及中間件;負責隨行付對外開源等事宜。
原文連接:https://my.oschina.net/matieli/blog/2992447