The introduction
Average engineers pile code, good engineers elegant code, great engineers simplify code. How to write elegant, clean and easy to understand code is a science and an important part of software engineering practice. — from the Internet
background
Software quality is not only dependent on architecture and project management, but also closely related to code quality. Concise and efficient code is not only easy to read, but also avoids potential bugs and risks and improves code quality. Recently, an Oracle programmer took to Hacker News to make fun of his work.
The engineer’s core pain point was that Oracle was going through long iterations of its product line, and the code was unusually large and logically complex, riddled with mysterious macro commands. Every time a new feature is added or a BUG is fixed, the engineer needs to do a lot of research and carefully carry out his daily work. Oracle has been tested millions of times with each release, which is a nightmare. So how do we write clean and efficient code? In fact, there are many books on this topic, such as the classic books “Code Clean”, “The Art of Writing Readable Code”, “Refactoring: Improving the Design of Existing Code”, which can be used to develop internal skills. And we have strict code specifications and convenient static code scanning tools that can be used to enhance the development of code quality capabilities.
Simplicity of
In fact, code specifications and static code scanning tools can help us accomplish a lot of code simplification. Comments, naming, methods, exceptions, unit tests, etc. But it is unable to summarize some code concise best practices, in fact, Java is object-oriented voice, and object-oriented features are encapsulation, inheritance, polymorphism, clever use of these three features, understand some of the Java keyword features, voice features, read JDK source code, you can write relatively concise code.
To simplify the logic
/ / modify before
if(list.size()>0) {
return true;
} else {
return false;
}
/ / modified
return list.size()>0;
Copy the code
- If /else syntax: The if statement contains a Boolean expression. When the Boolean expression value of the if statement is false, the else block is executed.
- Return keyword: Returns a value of any type;
- List.size ()>0: The list.size() method itself is a function that returns a value of type int, and forms a Boolean expression with >0;
Omit meaningless assignments
/ / modify before
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;
}
/ / modified
public List<Map<String, Object>> queryList(Map<String, Object> params) {
try {
return mapper.queryList(params);
} catch (Throwable e) {
throw new RuntimeException("ERROR", e); }}Copy the code
- The data type of the local variable list is the same as the return value type of the method, and any extra variables will add to JVM garbage collection;
- The local variable list is responsible for receiving the return value from mapper.queryList(Params), and there is no other logic involved;
- This code exists between the Service layer and the Mapper layer, and can be further abstracted at the framework level with annotations, java8 default methods, and so on.
Minimization judgment
/ / modify before
if (0 == retCode) {
sendMessage("A001"."Process Success", outResult);
} else {
sendMessage("A001"."Process Failure", outResult);
}
// After 1
String message = (0 == retCode ? "Process Success" : "Process Failure");
sendMessage("A001", message, outResult);
// Modify 2
sendMessage("A001", messageFromRetCode(retCode), outResult);
Copy the code
- The if else in the code is only there because the second argument to sendMessage has two cases (success/failure), so try to minimize the judgment;
Set method governance
/ / modify before
String uuid = UUIDUtils.getUUID();
String date = DateTimeUtils.getCurrDt();
String time = DateTimeUtils.getCurrTm();
Order order = new Order();
order.setSrUsrId(map.get("srcUsrId"));
// omit dozens of sets. order.setTmsDate(date); order.setTmsCte(time); order.setUuid(uuid); list.add(order);/ / modified
list.add(buildOrder(map));
public Order buildOrder(Map<String,String> map){
Order order = new Order();
order.setSrUsrId(map.get("srcUsrId"));
// omit dozens of sets. String date = DateTimeUtils.getCurrDt(); order.setTmsDate(date); order.setTmsCte(DateTimeUtils.getCurrTm()); String uuid = UUIDUtils.getUUID(); order.setUuid(uuid);return order;
}
Copy the code
- Large set methods can make code very difficult to read. You can encapsulate specific methods or use Lombok tools to simplify code.
- Local variable declaration nearby, increase readability, local variable declaration and use place far away, will cause readers to slide frequently;
- May not declare variables as far as possible do not declare redundant variables, redundant code; (such as date and time codes);
Smart use of JAVA8 features – functional programming to simplify code
JAVA8 features “functional programming”, what can we do with Lambdas?
- Iterate over sets (List, Map, etc.), Sum, Max, Min, Avg, Sort, Distinct, etc
- The function interface
- Predicate is used
- Implement Map and Reduce
- Implement event handling/simplify multithreading
Inner and outer circulation
/ / modify before
public static void test1(a) {
List<Integer> numbers = Arrays.asList(1.2.3.4.5.6);
for (intnumber : numbers) { System.out.println(number); }}// After 1
// Use lambda expressions and functional operations
public static void test2(a){
List<Integer> numbers = Arrays.asList(1.2.3.4.5.6);
numbers.forEach((Integer value)-> System.out.println(value));
}
// Modify 2
// use the double colon operator in Java8.
public static void test3(a){
List<Integer> numbers = Arrays.asList(1.2.3.4.5.6);
numbers.forEach(System.out::println);
}
Copy the code
The above code is the traditional way to iterate over a List. In short, there are three main shortcomings:
- The data in the list can only be processed sequentially (Process one by one)
- Multi-core cpus cannot be fully utilized
- Bad for compiler optimization (JIT)
Using functional programming can avoid the above three problems:
- Elements in a List need not be processed sequentially; the order can be arbitrary
- It can be processed in parallel, making full use of the advantages of multi-core CPUS
- It helps JIT compilers optimize code
- The code looks cleaner and is left entirely to the compiler’s internal loop
The default method
In Java8, methods in an interface can be implemented, identified by the keyword default as a modifier. Methods implemented in an interface are called default methods. With the default method, when the interface changes, the implementation class does not need to change, and all subclasses inherit the default method.
public class Test1 {
public static void main(String[] args) {
Formula formula = new Formula() {
@Override
public double calculate(int a) {
returnsqrt(a * 100); }}; System.out.println(formula.calculate(100)); / / System. 100.0 out. Println (formula. SQRT (16)); // 4.0}} interface Formula {double calculate(int a); default double sqrt(int a) {returnMath.sqrt(a); }}Copy the code
When an interface extends another interface that contains default methods, there are three ways to do this.
- Ignore the default methods completely (inherit the default methods of the parent interface directly)
- Redeclare the default method as abstract (no implementation, concrete subclasses must implement the method again)
- Re-implement the default method (rewrites the implementation of the default method, still a default method)
Date processing
With LocalDate and LocalTime interfaces added to Java8, why a new API for handling dates and times? Because the old java.util.Date was just too hard to use.
- Java.util.Date starts at 0. January is 0 and December is 11. LocalDate = java.time.LocalDate = month/week
- Java.util. Date and SimpleDateFormatter are not thread-safe, while LocalDate and LocalTime, like the most basic String, are immutable types that are thread-safe and cannot be modified.
- Date is a “universal interface” that contains dates, times, and milliseconds. If you only want to use java.util.Date to store dates, or just times, only you know which parts of the data are useful and which parts are not. In the new Java8, dates and times are explicitly divided into LocalDate and LocalTime. LocalDate cannot contain time, and LocalTime cannot contain dates.
Of course, LocalDateTime can contain both date and time.
The new interface is better because it takes into account date-time operations, which are often pushed back or forward by a few days. Using java.util.Date with Calendar is a lot of code to write, and the average developer may not get it right.
- 1. The Clock class provides methods to access the current date and time. Clock is time-zone sensitive and can be used instead of System.currentTimemillis () to get the current microsecond. A particular point in time can also be represented using Instant classes (Final classes), which can also be used to create old java.util.date objects.
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 is accurate to nanosecond, which is much more accurate than date in milliseconds
// Get the current time
Instant in = Instant.now();
System.out.println(in);
// Increase the current time by 3 hours and 2 minutes to create a new instance
Instant in1 = in.plus(Duration.ofHours(3).plusMinutes(2));
System.out.println(in1);
System.out.println(in1 == in);
// Examples of calculations
in.minus(5, ChronoUnit.DAYS);// Calculate 5 days ago
in.minus(Duration.ofDays(5));// Calculate 5 days ago
// Calculate the number of minutes between two Instant
long diffAsMinutes1 = ChronoUnit.MINUTES.between(in, in1); 2 / / method
System.out.println(diffAsMinutes1);
// Instant is comparable with isAfter and isBefore
System.out.println(in.isAfter(in1));
System.out.println(in.isBefore(in1));
Copy the code
- 2, LocalDate and LocalTime, LocalDateTime(Final class, no time zone) a series of calculations. LocalDateTime and Instant are similar in that they are both dates and times without time zones. Instant is an Instant point in time without time zones. For example, they were both born on April 14, 2018. One was born in Beijing and the other in New York. It looks like they were born together (LocalDateTime semantics), but they actually have time differences (Instant semantics).
// Take the current date
LocalDate today = LocalDate.now();
System.out.println(today);
// Get the 86th day of 2005 (27-Mar-2005)
LocalDate localDate = LocalDate.ofYearDay(2005.86);
System.out.println(localDate);
// Take the date as August 10, 2013
localDate = LocalDate.of(2013, Month.AUGUST, 10);
localDate = LocalDate.of(2013.8.10);
// Based on the string
LocalDate.parse("2014-02-28"); // Use ISO YYYY-MM-DD to verify that 02 is 2
LocalDate.parse("2014-02-29"); // Invalid date cannot pass: DateTimeParseException: Invalid date
// select 1st day of this month:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
// on the 2nd day of this month:
LocalDate secondDayOfThisMonth = today.withDayOfMonth(2);
// take the last day of the month, no longer counting 28,29,30 or 31:
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
// Take the next day:
LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // It becomes 2015-01-01
// On the first Monday of January 2015, this calculation will kill a lot of brain cells using Calendar:
LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01")
.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
// LocalTime
LocalTime now = LocalTime.now(); / / take a nanosecond
LocalTime now1 = LocalTime.now().withNano(0); // Clear nanoseconds
System.out.println(now);
System.out.println(now1);
LocalTime localTime = LocalTime.of(22.33);
System.out.println(localTime);
// Return to 4503 seconds of the day
localTime = LocalTime.ofSecondOfDay(4503);
System.out.println(localTime);
LocalDateTime localDateTime0 = LocalDateTime.now();
System.out.println(localDateTime0);
// Current time plus 25 hours and 3 minutes
LocalDateTime inTheFuture = localDateTime0.plusHours(25).plusMinutes(3);
System.out.println(inTheFuture);
// Can also be used with localTime and localDate
System.out.println(localDateTime0.toLocalTime().plusHours(25).plusMinutes(3));
System.out.println(localDateTime0.toLocalDate().plusMonths(2));
Copy the code
Streams and collection
Stream is a wrapper around a collection and is often used with lambda. A number of operations can be supported using lambdas. Such as map, filter, limit, sorted, count, min, Max, sum, collect and so on. Also, streams use lazy operations; they don’t actually read all the data. A method like getFirst() would end the chain syntax, as illustrated by the following series of examples: Let’s say I have a Person class, which is a simple POJO, for which we might have a list of operations.
class Person{ private String name, job, gender; private int salary, age; // omit several get/setMethods and construction methods... }Copy the code
- Data initialization is carried out first to provide a basis for the subsequent demonstration of operations
List<Person> persons = new ArrayList<Person>() {
private static final long serialVersionUID = 1L;
{
add(new Person("Zhang"."Java"."female", 25, 1000));
add(new Person("Bill"."Java"."male", 29, 1200));
add(new Person("Fifty"."Test"."female", 25, 1400));
add(new Person("Daisy"."Java"."male", 31, 1800));
add(new Person("Jonathan"."Design"."male", 33, 1900));
add(new Person("Li Si si"."Demand"."female", 30, 2000));
add(new Person("Wang Wu Wu"."Java"."female", 29, 2100));
add(new Person("Zhao Liu Liu"."Java"."male", 43, 2800)); }};Copy the code
- 1. Run foreach to output the preceding list
Consumer<Person> print = e -> System.out.println(e.toString());
persons.forEach(print);
Copy the code
- 2. Raise all employees’ salaries by 10%(using Foreach)
Consumer<Person> raise = e -> e.setSalary(e.getSalary()/100*10+e.getSalary());
persons.forEach(raise);
persons.forEach(print);
Copy the code
- 3. Show employees whose salary is less than 1500 (stream().filter())
persons.stream().filter((p) -> (p.getSalary()< 1500)).forEach(print);
Copy the code
- > 2000 job= Java age >29
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);
Copy the code
- 5. Limit the number of results
persons.stream().filter(genderPredicate).limit(2).forEach(print);
Copy the code
- Sort by age
persons.stream().sorted((p1,p2)-> (p1.getAge() - p2.getAge()))
//.sorted((p1,p2)->(p1.getName().compareTo(p2.getName())))
.forEach(print);
Copy the code
- 7. Find the highest salary Max () and the youngest age Min ()
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());
Copy the code
- Calculate all people’s wages
System.out.println("The sum of all the people's wages:"+ persons.stream().parallel().mapToInt(p - > p.getSalary()).sum());
Copy the code
- 9. Store the person’s name in TreeSet\set\String
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());
Copy the code
- 10. Statistical Results summaryStatistics()
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("The largest number in List:" + stats.getMax());
System.out.println("Smallest number in List:" + stats.getMin());
System.out.println("Sum of all numbers:" + stats.getSum());
System.out.println("Average of all numbers:" + stats.getAverage());
Copy the code
- 11. Remove duplicate elements and create a new array
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);
Copy the code
Passing behavior, not just passing values
// The sumAll algorithm is simple. What it does is add all the elements in the List. public static int sumAll(List<Integer> numbers) { int total = 0;for (int number : numbers) {
total += number;
}
return total;
}
Copy the code
The sumAll algorithm is simple, and all it does is add up all the elements in the List. One day if we need to add sumAllEven to a List of even numbers, then we get sumAll2, as follows:
public static int sumAll2(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
if(number % 2 == 0) { total += number; }}return total;
}
Copy the code
Another day, we need to add a third method: the sum of all elements in the List greater than 3. sumAll3
public static int sumAll3(List<Integer> numbers) {
int total = 0;
for (int number : numbers) {
if(number > 3) { total += number; }}return total;
}
Copy the code
The only difference is that the if condition in the method is different (the first one can be considered as if(true)). If we were to optimize, the first refactoring that might come to mind would be the policy pattern, which looks like this:
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; }}returntotal; } // Call BodyClass BC = new BodyClass(); bc.sumAll(numbers);Copy the code
This no doubt optimizes the redundant code in a design pattern manner, but it may require adding a few additional classes and later extensions. Here’s how to implement using lambda, declaring methods: The first argument is the List array we passed in earlier. The second one might look a little strange, but you can see by looking at the JDK that this class is a predicate (Boolean function).
public static int sumAllByPredicate(List<Integer> numbers, Predicate<Integer> p) {
int total = 0;
for (int number : numbers) {
if(p.test(number)) { total += number; }}returntotal; } // Call: sumAllByPredicate(numbers, n ->true);
sumAllByPredicate(numbers, n -> n % 2 == 0);
sumAllByPredicate(numbers, n -> n > 3);
Copy the code
Isn’t the code much cleaner than above? The semantics are also very clear, and the important thing is that no matter how it changes later, it can be changed in one line of code… Snake oil.
other
JAVA8 also introduced a number of features to simplify code. Examples include the String.join function, the Objects class, and the Base64 encoding class.
String splicing
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);
Copy the code
What kind of
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)){
}
Copy the code
Base64 encoding
Base64.Encoder encoder = Base64.getEncoder();
Base64.Decoder decoder = Base64.getDecoder();
String str = encoder.encodeToString("Hello".getBytes(StandardCharsets.UTF_8));
System.out.println(str);
System.out.println(new String(decoder.decode(str),StandardCharsets.UTF_8));
Copy the code
conclusion
Good code needs to be polished, and as good engineers, we should stick to it and deliver better code each time than when we shipped it out. It is often said that being an engineer must have team spirit, but this spirit is not just talk about it, it needs actual actions to reflect it. Design patterns, new features in the JDK are lessons we can draw from. After coding, we can think about whether we can simplify and optimize, and don’t become a “evil” engineer.
Author’s brief introduction
Ma Tieli, head of entourage payment Architecture Department & member of Beijing Branch of TGO Kunpeng Club, full-stack engineer for 10 years, good at micro-service distributed architecture design. Mainly responsible for the daily management of entourage payment structure Department; Participate in the construction of peripheral infrastructure and middleware of micro-service platform; Responsible for external open source payment and other matters.