предисловие
За четыре года работы я видел много недостаточно глубокого кода, поэтому напишите резюме, 50 предложений по улучшению вашего кода. Некоторые из этих моментов я также писал в предыдущих статьях, на этот раз я подытожу их. Я надеюсь, что каждый будет больше думать о написании кода каждый день и больше подводить итоги. В то же время, если что-то не так, я надеюсь указать на это, спасибо~
- публика:маленький мальчик собирает улиток
- адрес гитхаба:GitHub.com/Я бы хотел 123/Java…
1. Если только судить о том, существует ли он, выберите количество лучше, чем выберите определенные столбцы.
Мы часто сталкиваемся с похожими бизнес-сценариями, такими как оценка пользователя.userId
Является членом.
(Контрпример):Некоторые мелкие партнеры сделают это, сначала проверив запись пользователя из таблицы сведений о пользователе, а затем определяя, является ли он участником:
<select id="selectUserByUserId" resultMap="BaseResultMap">
selct user_id , vip_flag from user_info where user_id =#{userId};
</select>
boolean isVip (String userId){
UserInfo userInfo = userInfoDAp.selectUserByUserId(userId);
return UserInfo!=null && "Y".equals(userInfo.getVipFlag())
}
(положительный пример):Фактически, для этого бизнес-сценария лучшей реализацией является прямоеselect count
Теперь следующим образом:
<select id="countVipUserByUserId" resultType="java.lang.Integer">
selct count(1) from user_info where user_id =#{userId} and vip_flag ='Y';
</select>
boolean isVip (String userId){
int vipNum = userInfoDAp.countVipUserByUserId(userId);
return vipNum>0
}
2. Сложные, если логические условия можно настроить, чтобы сделать программу более эффективной.
Предположим, бизнес-требование таково: если пользователь является участником и при первом входе в систему ему необходимо отправить SMS-уведомление. Если нет мысли, код, скорее всего, будет написан так.
if(isUserVip && isFirstLogin){
sendMsgNotify();
}
Предположим, всего приходит 5 запросов, 3 запроса проходят через isUserVip и 1 запрос проходит через isFirstLogin. Затем приведенный выше код isUserVip выполняется 5 раз, isFirstLogin выполняется 3 раза, как показано ниже:
Что если изменить порядок isUserVip и isFirstLogin?
if(isFirstLogin && isUserVip ){
sendMsg();
}
Количество выполнений isFirstLogin — 5 раз, а количество выполнений isUserVip — 1 раз, как показано ниже:
Если ваш isFirstLogin, логика решения состоит только в том, чтобы выбрать подсчет таблицы базы данных, isUserVip также выберите подсчет таблицы базы данных, очевидно, более эффективно разместить isFirstLogin впереди.
3. При написании запроса Sql проверяйте только поля, которые необходимо использовать, а также общие поля, отклоняйте выбор слева *
Пример счетчика:
select * from user_info where user_id =#{userId};
Положительный пример:
selct user_id , vip_flag from user_info where user_id =#{userId};
причина:
- Экономьте ресурсы и снижайте нагрузку на сеть.
- Покрывающие индексы могут использоваться для уменьшения возвращаемых таблиц и повышения эффективности запросов.
4. Оптимизируйте свою программу и откажитесь от создания ненужных объектов
Если ваша переменная, то последующее логическое суждение обязательно будет присвоено, другими словами, это просто строковая переменная, вы можете напрямую инициализировать строковую константу, нет необходимости использовать new String().
Пример счетчика:
String s = new String ("欢迎关注公众号:捡田螺的小男孩");
Положительный пример:
String s= "欢迎关注公众号:捡田螺的小男孩 ”;
5. При инициализации коллекции укажите емкость
В руководстве по разработке Али также четко упоминается этот момент:
Предполагая, что количество элементов, которые будут храниться на вашей карте, составляет около 15, оптимальный способ записи выглядит следующим образом.
//initialCapacity = 15/0.75+1=21
Map map = new HashMap(21);
又因为hashMap的容量跟2的幂有关,所以可以取32的容量
Map map = new HashMap(32);
6. Чтобы поймать исключение, вам нужно распечатать конкретное исключение, чтобы лучше определить проблему.
Пример счетчика:
try{
// do something
}catch(Exception e){
log.info("捡田螺的小男孩,你的程序有异常啦");
}
Положительный пример:
try{
// do something
}catch(Exception e){
log.info("捡田螺的小男孩,你的程序有异常啦:",e); //把exception打印出来
}
причина:
- В контрпримере исключение не выводится, и исследовать проблему в это время будет сложно.Это исключение, написанное SQl неправильно, исключение IO или что-то еще? Таким образом, исключение должно быть напечатано в журнале~
7. При печати журнала объект не переопределяет метод toString объекта Object и печатает имя класса напрямую.
Когда мы печатаем журнал, мы часто хотим увидеть, какой следующий запрос объекта параметра запроса. Таким образом, легко иметь код, подобный следующему:
publick Response dealWithRequest(Request request){
log.info("请求参数是:".request.toString)
}
Результат печати следующий:
请求参数是:local.Request@49476842
Это связано с тем, что реализация метода toString объекта по умолчанию — «classname@hashcode unsigned hex». Итак, вы видите, бессмысленно печатать лог таким образом, вы не знаете, что печатается.
Таким образом, общий объект (особенно как объект параметра),Переопределить метод toString():
class Request {
private String age;
private String name;
@Override
public String toString() {
return "Request{" +
"age='" + age + '\'' +
", name='" + name + '\'' +
'}';
}
}
publick Response dealWithRequest(Request request){
log.info("请求参数是:".request.toString)
}
Результат печати следующий:
请求参数是:Request{age='26', name='公众号:捡田螺的小男孩'}
8. Метод, отвергающий длинные списки параметров.
Предположим, есть такой публичный метод с четырьмя формальными параметрами. . .
public void getUserInfo(String name,String age,String sex,String mobile){
// do something ...
}
Если вам сейчас нужно передать еще один параметр версии, а ваш общедоступный метод представляет собой внешний интерфейс, такой как dubbo, должен ли ваш интерфейс быть совместимым со старой версией?
public void getUserInfo(String name,String age,String sex,String mobile){
// do something ...
}
/**
* 新接口调这里
*/
public void getNewUserInfo(String name,String age,String sex,String mobile,String version){
// do something ...
}
Поэтому, как правило, параметры метода не должны быть слишком длинными. Чрезмерно длинный список параметров не только выглядит неэлегантно, но и учитывает совместимость между новой и старой версиями при обновлении интерфейса. Что делать, если параметров слишком много? Вы можете обернуть эти параметры объектом DTO ~ следующим образом:
public void getUserInfo(UserInfoParamDTO userInfoParamDTO){
// do something ...
}
class UserInfoParamDTO{
private String name;
private String age;
private String sex;
private String mobile;
}
Оберните его в объект DTO, даже если позже произойдет изменение параметра, вам не нужно перемещать внешний интерфейс, что очень удобно.
9. Используйте буферизованные потоки, чтобы уменьшить количество операций ввода-вывода
Пример счетчика:
/**
* 公众号:捡田螺的小男孩
* @desc: 复制一张图片文件
*/
public class MainTest {
public static void main(String[] args) throws FileNotFoundException {
long begin = System.currentTimeMillis();
try (FileInputStream input = new FileInputStream("C:/456.png");
FileOutputStream output = new FileOutputStream("C:/789.png")) {
byte[] bytes = new byte[1024];
int i;
while ((i = input.read(bytes)) != -1) {
output.write(bytes,0,i);
}
} catch (IOException e) {
log.error("复制文件发生异常",e);
}
log.info("常规流读写,总共耗时ms:"+(System.currentTimeMillis() - begin));
}
}
результат операции:
常规流读写,总共耗时ms:52
использоватьFileInputStream
,FileOutputStream
Нет проблем в реализации функции чтения и записи файлов. Однако можно использовать буферизованные потоки.BufferedReader
,BufferedWriter
,BufferedInputStream
,BufferedOutputStream
д., сократить время ввода-вывода и повысить эффективность чтения и записи.
Если это небуферизованный поток, то при чтении байта или символа данные будут выведены напрямую. Для буферизованного потока при чтении байта или символа он сначала не будет выводиться, а будет выводиться за один раз, когда будет достигнута максимальная емкость буфера.
Положительный пример:
/**
* 公众号:捡田螺的小男孩
* @desc: 复制一张图片文件
*/
public class MainTest {
public static void main(String[] args) throws FileNotFoundException {
long begin = System.currentTimeMillis();
try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:/456.png"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:/789.png"))) {
byte[] bytes = new byte[1024];
int i;
while ((i = input.read(bytes)) != -1) {
output.write(bytes,0,i);
}
} catch (IOException e) {
log.error("复制文件发生异常",e);
}
log.info("总共耗时ms"+(System.currentTimeMillis() - begin));
}
}
результат операции:
缓冲流读写,总共耗时ms:12
10. Оптимизируйте логику вашей программы.Например, если данные, которые были найдены ранее, также используются в более поздних методах, вы можете передавать параметры вниз, чтобы уменьшить вызовы методов/справочные таблицы.
Пример счетчика:
public Response dealRequest(Request request){
UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
if(Objects.isNull(request)){
return ;
}
insertUserVip(request.getUserId);
}
private int insertUserVip(String userId){
//又查了一次
UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
//插入用户vip流水
insertUserVipFlow(userInfo);
....
}
Очевидно, что приведенный выше программный код уже проверил userInfo, а затем передал вниз userId и снова проверил его. . . На самом деле, userInfo можно передавать по наследству, что экономит операцию поиска в таблице и делает программу более эффективной.
Положительный пример:
public Response dealRequest(Request request){
UserInfo userInfo = userInfoDao.selectUserByUserId(request.getUserId);
if(Objects.isNull(request)){
return ;
}
insertUserVip(userInfo);
}
private int insertUserVip(UserInfo userInfo){
//插入用户vip流水
insertUserVipFlow(userInfo);
....
}
11. Не используйте для удобства магические значения типа 0, 1 прямо в коде, вместо этого следует использовать перечисление enum.
Пример счетчика:
if("0".equals(userInfo.getVipFlag)){
//非会员,提示去开通会员
tipOpenVip(userInfo);
}else if("1".equals(userInfo.getVipFlag)){
//会员,加勋章返回
addMedal(userInfo);
}
Положительный пример:
if(UserVipEnum.NOT_VIP.getCode.equals(userInfo.getVipFlag)){
//非会员,提示去开通会员
tipOpenVip(userInfo);
}else if(UserVipEnum.VIP.getCode.equals(userInfo.getVipFlag)){
//会员,加勋章返回
addMedal(userInfo);
}
public enum UserVipEnum {
VIP("1","会员"),
NOT_VIP("0","非会员"),:;
private String code;
private String desc;
UserVipEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
}
При написании кода не используйте ману по прихоти. С маной сложно поддерживать код.
12. Когда значение переменной-члена не изменится, предпочтительно определить его как статическую константу.
Пример счетчика:
public class Task {
private final long timeout = 10L;
...
}
Положительный пример:
public class Task {
private static final long TIMEOUT = 10L;
...
}
Потому что, если он определен как статический, то есть статическая константа класса, он имеет только одну копию в каждом экземпляре объекта. Если это переменная-член, у каждого экземпляра объекта есть копия. Очевидно, что если переменная не изменяется, лучше определить ее как статическую константу.
13. Обратите внимание на проверку нулевого указателя, не верьте легко делу, говоря, что параметр не может быть нулевым в нормальной логике.
NullPointerException очень часто встречается в нашей повседневной разработке, поэтому во время разработки нашего кода мы должны хорошо понимать нулевой указатель.
В основном существуют следующие типы проблем с нулевым указателем:
- Нулевой указатель проблема с типами обертки
- Проблема с нулевым указателем при каскадных вызовах
- Проблема с нулевым указателем в левой части метода Equals
- Контейнеры, подобные ConcurrentHashMap, не поддерживают k-v как null.
- Коллекции, массивы получают элементы напрямую
- Объект получает свойства напрямую
Пример счетчика:
public class NullPointTest {
public static void main(String[] args) {
String s = null;
if (s.equals("666")) { //s可能为空,会导致空指针问题
System.out.println("公众号:捡田螺的小男孩,干货满满");
}
}
}
14. Пойманное исключение нельзя игнорировать, хотя бы логировать.
Пример счетчика:
public static void testIgnoreException() throws Exception {
try {
// 搞事情
} catch (Exception e) {
//捕获了异常,啥事情不做,日志也不打??
}
}
Положительный пример:
public static void testIgnoreException() {
try {
// 搞事情
} catch (Exception e) {
log.error("异常了,联系开发小哥哥看看哈",e);
}
}
15. Используйте лямбда-выражения для замены внутренних анонимных классов, чтобы сделать код более элегантным.
В JDK8 появилась новая функция — лямбда-выражения. Лямбда-выражения не только более элегантны, чем анонимные внутренние классы, но и реализованы с помощью инструкций invokeDynamic в большинстве виртуальных машин, которые более эффективны, чем анонимные внутренние классы.
Пример счетчика:
public void sortUserInfoList(List<UserInfo> userInfoList){
userInfoList.sort(new Comparator<UserInfo>() {
@Override
public int compare(UserInfo user1, UserInfo user2) {
Long userId1 = user1.getUserId();
Long userId2 = user2.getUserId();
return userId1.compareTo(userId2);
}});
}
Положительный пример:
public void sortUserInfoList(List<UserInfo> userInfoList){
userInfoList.sort((user1, user2) -> {
Long userId1 = user1.getUserId();
Long userId2 = user2.getUserId();
return userId1.compareTo(userId2);
});
}
16. Код класса уведомлений (например, отправка электронных писем и текстовых сообщений) рекомендуется обрабатывать асинхронно.
Предположим, бизнес-процесс такой: когда пользователь входит в систему, нужно добавить текстовое сообщение, чтобы уведомить его поклонников. Процесс реализации легко представить следующим образом:
Предполагая, что система, предоставляющая службу sendMsgNotify, зависает или вызов sendMsgNotify завершается сбоем, пользователь не может войти в систему. . . Функция уведомления сделала недоступным основной процесс входа в систему, очевидно собирая семена кунжута и бросая арбузы. Так есть ли способ получить и то, и другое? Да, перехватите обработку исключений для текстового интерфейса или откройте отдельный поток для асинхронной обработки следующим образом:
Поэтому при добавлении классов уведомлений и прочих несущественных, деградируемых интерфейсов стоит успокоиться и подумать, не повлияет ли это на основной процесс и как с этим лучше поступить.
17. Остерегайтесь проблем с форматированием YYYY при работе с датами Java.
В повседневной разработке нам часто приходится иметь дело с датами. Когда мы хотим, чтобы дата была отформатирована, год указывается в верхнем регистре.YYYY
яма.
Calendar calendar = Calendar.getInstance();
calendar.set(2019, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2019-12-31 转 YYYY-MM-dd 格式后 " + dtf.format(testDate));
результат операции:
2019-12-31 转 YYYY-MM-dd 格式后 2020-12-31
Почему очевидно, что 31 декабря 2019 года, чтобы изменить формат, стало в 2020 году 31 декабря? Потому что YYYY основан на подсчете недель и указывает на год, в котором день принадлежит неделе, считая с начала недели в воскресенье, заканчивая субботой, только на этой неделе, в канун Нового года, поэтому на этой неделе, даже если следующий год. Правильная позиция заключается в использовании формата yyyy.
18. Если определено, что класс не подлежит наследованию и не используется для АОП-операций, можно указать модификатор final, например, изменить класс инструмента с помощью final.
Положительный пример:
public final class Tools {
public static void testFinal(){
System.out.println("工具类方法");
}
}
Класс, указанный модификатором final, не будет унаследован, и все его методы являются окончательными. Компилятор Java найдет возможность встроить все конечные методы, что повысит эффективность работы Java.
19. Не полагайтесь на переменные экземпляра Spring для статических статических переменных, которые могут вызвать ошибки инициализации.
Я уже видел проекты с подобным кодом. Статические переменные зависят от bean-компонентов контейнера Spring.
private static SmsService smsService = SpringContextUtils.getBean(SmsService.class);
Этот статический smsService может быть недоступен, поскольку порядок загрузки классов не определен, а в приведенном выше коде инициализация статического smsService вынуждена полагаться на экземпляр контейнера Spring. Правильный способ написания выглядит следующим образом:
private static SmsService smsService =null;
//使用到的时候采取获取
public static SmsService getSmsService(){
if(smsService==null){
smsService = SpringContextUtils.getBean(SmsService.class);
}
return smsService;
}
20. Методы, не связанные с переменными-членами класса, должны быть объявлены как статические методы.
Некоторые методы, независимые от переменных-членов экземпляра, могут быть объявлены как статические методы. В связи с этим инструментов используется много.Вопреки следующему:
/**
* BigDecimal的工具类
*/
public class BigDecimalUtils {
public BigDecimal ifNullSetZERO(BigDecimal in) {
return in != null ? in : BigDecimal.ZERO;
}
public BigDecimal sum(BigDecimal ...in){
BigDecimal result = BigDecimal.ZERO;
for (int i = 0; i < in.length; i++){
result = result.add(ifNullSetZERO(in[i]));
}
return result;
}
Поскольку методы класса инструментов BigDecimalUtils не изменяются статически, когда вы хотите их использовать, вам нужно каждый раз обновлять их, иначе они будут потреблять ресурсы.Многократное создание объектовНу давай же! !
BigDecimalUtils bigDecimalUtils = new BigDecimalUtils();
bigDecimalUtils.sum(a,b);
Поэтому ее можно объявить как статическую переменную, при ее использовании напрямую类名.方法
Просто позвоните, например:
/**
* BigDecimal的工具类
*/
public class BigDecimalUtils {
public static BigDecimal ifNullSetZERO(BigDecimal in) {
return in != null ? in : BigDecimal.ZERO;
}
public static BigDecimal sum(BigDecimal ...in){
BigDecimal result = BigDecimal.ZERO;
for (int i = 0; i < in.length; i++){
result = result.add(ifNullSetZERO(in[i]));
}
return result;
}
21. Не используйте Exception для перехвата всех возможных исключений.
Пример счетчика:
public void test(){
try{
//…抛出 IOException 的代码调用
//…抛出 SQLException 的代码调用
}catch(Exception e){
//用基类 Exception 捕捉的所有可能的异常,如果多个层次都这样捕捉,会丢失原始异常的有效信息哦
log.info(“Exception in test,exception:{}”, e);
}
}
Положительный пример:
public void test(){
try{
//…抛出 IOException 的代码调用
//…抛出 SQLException 的代码调用
}catch(IOException e){
//仅仅捕捉 IOException
log.info(“IOException in test,exception:{}”, e);
}catch(SQLException e){
//仅仅捕捉 SQLException
log.info(“SQLException in test,exception:{}”, e);
}
}
22. Не переусердствуйте с функциями, просто будьте лаконичны.
Пример счетчика:
// 函数封装
public static boolean isUserVip(Boolean isVip) {
return Boolean.TRUE.equals(isVip);
}
// 使用代码
boolean isVip = isVip(user.getUserVip());
Положительный пример:
boolean isVip = Boolean.TRUE.equals(user.getUserVip());
Не переусердствуйте с функцией, просто ясно выражайте смысл. Кроме того, вызовы методов вызовут стекирование и выталкивание, что приведет к большему потреблению ЦП и памяти, а также к чрезмерной инкапсуляции, которая будет снижать производительность!
23. Если начальное значение переменной будет перезаписано, нет необходимости присваивать начальное значение переменной.
Пример счетчика:
List<UserInfo> userList = new ArrayList<>();
if (isAll) {
userList = userInfoDAO.queryAll();
} else {
userList = userInfoDAO.queryActive();
}
Положительный пример:
List<UserInfo> userList ;
if (isAll) {
userList = userInfoDAO.queryAll();
} else {
userList = userInfoDAO.queryActive();
}
24. При численном расчете суммы следует использовать BigDecimal
Взгляните на этот пример арифметики с плавающей запятой:
public class DoubleTest {
public static void main(String[] args) {
System.out.println(0.1+0.2);
System.out.println(1.0-0.8);
System.out.println(4.015*100);
System.out.println(123.3/100);
double amount1 = 3.15;
double amount2 = 2.10;
if (amount1 - amount2 == 1.05){
System.out.println("OK");
}
}
}
результат операции:
0.30000000000000004
0.19999999999999996
401.49999999999994
1.2329999999999999
Поскольку компьютеры хранят числа в двоичном формате, то же самое верно и для чисел с плавающей запятой. Для компьютера 0,1 не может быть выражено точно, поэтому числа с плавающей запятой вызывают недостаток точности. Поэтому при расчете суммы обычно используется тип BigDecimal.
System.out.println(new BigDecimal(0.1).add(new BigDecimal(0.2)));
//output:
0.3000000000000000166533453693773481063544750213623046875
На самом деле, чтобы использовать BigDecimal для представления и вычисления чисел с плавающей запятой, вы должны использовать конструктор строк для инициализации BigDecimal и обращать внимание на десятичные точки BigDecimal, который имеет восемь режимов округления и т. д.
25. Обратите внимание на подводные камни Arrays.asList
- Примитивный тип нельзя использовать в качестве параметра метода Arrays.asList, иначе он будет рассматриваться как параметр.
public class ArrayAsListTest {
public static void main(String[] args) {
int[] array = {1, 2, 3};
List list = Arrays.asList(array);
System.out.println(list.size());
}
}
//运行结果
1
- Список, возвращаемый Arrays.asList, не поддерживает добавление или удаление.
public class ArrayAsListTest {
public static void main(String[] args) {
String[] array = {"1", "2", "3"};
List list = Arrays.asList(array);
list.add("5");
System.out.println(list.size());
}
}
// 运行结果
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at object.ArrayAsListTest.main(ArrayAsListTest.java:11)
Список, возвращаемый Arrays.asList, — это не ожидаемый java.util.ArrayList, а внутренний класс ArrayList массивов. ArrayList внутреннего класса реализует не метод добавления, а реализацию метода добавления родительского класса, который вызовет исключение.
- При использовании Arrays.asLis модификации исходного массива повлияют на список, который мы получаем.
public class ArrayAsListTest {
public static void main(String[] args) {
String[] arr = {"1", "2", "3"};
List list = Arrays.asList(arr);
arr[1] = "4";
System.out.println("原始数组"+Arrays.toString(arr));
System.out.println("list数组" + list);
}
}
//运行结果
原始数组[1, 4, 3]
list数组[1, 4, 3]
26. Вовремя закройте поток ресурсов ввода-вывода
У каждого должен был быть такой опыт.Если вы откроете слишком много файлов или системного программного обеспечения на системном рабочем столе Windows, вы почувствуете, что компьютер сильно зависает. Конечно, то же самое верно и для нашего Linux-сервера.Мы обычно оперируем файлами или соединениями с базой данных.Если поток ресурсов ввода-вывода не закрыт, то этот ресурс ввода-вывода будет занят им, так что другие не смогут его использовать, что приведет к пустой трате ресурсов.
Поэтому после использования потока ввода-вывода не забудьте закрыть его. Можно закрыть с помощью try-with-resource:
/*
* 关注公众号,捡田螺的小男孩
*/
try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) {
// use resources
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
27. Попробуйте использовать примитивные временные переменные внутри функций
- В функции метода параметры базового типа и временные переменные хранятся в стеке, а скорость доступа относительно высока.
- Параметры типа объекта и ссылки на временные переменные хранятся в стеке, а содержимое хранится в куче, скорость доступа низкая.
- В классе переменные-члены любого типа хранятся в куче (Heap), доступ к которой медленнее.
public class AccumulatorUtil {
private double result = 0.0D;
//反例
public void addAllOne( double[] values) {
for(double value : values) {
result += value;
}
}
//正例,先在方法内声明一个局部临时变量,累加完后,再赋值给方法外的成员变量
public void addAll1Two(double[] values) {
double sum = 0.0D;
for(double value : values) {
sum += value;
}
result += sum;
}
}
28. Если количество запросов в базе данных слишком велико, рекомендуется выполнить разбиение на страницы.
Если ваш Sql извлекает большой объем данных за один раз, рекомендуется пейджинг.
Пример счетчика:
select user_id,name,age from user_info ;
Положительный пример:
select user_id,name,age from user_info limit #{offset},#{pageSize};
Если смещение особенно велико, эффективность запроса становится низкой. Его можно оптимизировать следующим образом:
//方案一 :返回上次查询的最大记录(偏移量)
select id,name from user_info where id>10000 limit #{pageSize}.
//方案二:order by + 索引
select id,name from user_info order by id limit #{offset},#{pageSize}
//方案三:在业务允许的情况下限制页数:
29. Сведите к минимуму двойной подсчет переменных
Как правило, когда мы пишем код, мы реализуем обход следующими способами:
for (int i = 0; i < list.size; i++){
}
Если объем данных списка относительно невелик, это нормально. Если список относительно большой, его можно оптимизировать следующим образом:
for (int i = 0, int length = list.size; i < length; i++){
}
причина:
- Вызов метода, даже если он имеет только один оператор, является одноразовым, например, создание кадра стека. Если список относительно велик, многократный вызов list.size также будет потреблять ресурсы.
30. При изменении старого внешнего интерфейса учитывайте совместимость интерфейса.
Многие ошибки вызваны изменением старого внешнего интерфейса, но не обеспечением его совместимости. Ключевым моментом является то, что большинство этих проблем являются серьезными и могут напрямую привести к сбою выпуска системы. Начинающим программистам легко допустить эту ошибку~
Поэтому, если вам нужно изменить исходный интерфейс, особенно если этот интерфейс предоставляет услуги внешнему миру, вы должны учитывать совместимость интерфейса. Например, dubbo интерфейс изначально получал только параметры A и B. Теперь вы добавляете параметр C, можете это учитывать.
//老接口
void oldService(A,B);{
//兼容新接口,传个null代替C
newService(A,B,null);
}
//新接口,暂时不能删掉老接口,需要做兼容。
void newService(A,B,C);
31 Код предпринимает шаги, чтобы избежать ошибок во время выполнения (таких как переполнение границ массива, деление на ноль и т. д.)
В повседневной разработке нам необходимо принимать меры, чтобы избежать ошибок времени выполнения, таких как переполнение границы массива, делимость на ноль и нулевой указатель.
Подобный код встречается чаще:
String name = list.get(1).getName(); //list可能越界,因为不一定有2个元素哈
Поэтому следует принимать меры по предотвращению выхода за границы массива,Положительный пример:
if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){
String name = list.get(1).getName();
}
32. Обратите внимание на яму принудительного вращения ArrayList.toArray()
public class ArrayListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(1);
list.add("公众号:捡田螺的小男孩");
String[] array21 = (String[])list.toArray();//类型转换异常
}
}
Поскольку возвращаемый объект имеет тип Object, при приведении массива типа Object к массиву String возникает исключение ClassCastException. Решение состоит в том, чтобы перегрузить метод toArray(T[] a) с помощью toArray().
String[] array1 = list.toArray(new String[0]);//可以正常运行
33. Старайтесь не совершать удаленные вызовы или операции с базой данных в цикле и отдавать приоритет пакетам.
Программные операции или операции с базой данных потребляют больше сетевых ресурсов и ресурсов ввода-вывода, поэтому старайтесь не выполнять удаленные вызовы в цикле и не работать с базой данных в цикле.Вы можете выполнять возврат в пакетном режиме и стараться не зацикливаться на многих раз. (Тем не менее, не проверяйте слишком много данных за один раз, вам придется одновременно обрабатывать 500 соусов)
Положительный пример:
remoteBatchQuery(param);
Пример счетчика:
for(int i=0;i<n;i++){
remoteSingleQuery(param)
}
34. После написания кода подумайте, что будет с многопоточным выполнением, обратите внимание на проблему параллелизма и согласованности
Некоторые бизнес-сценарии, которые мы часто видим, заключаются в том, чтобы сначала проверить наличие записей, а затем выполнить соответствующие операции (например, модификацию). Однако комбинация (запрос + модификация) не является атомарной операцией, и если вы задумаетесь о многопоточности, то обнаружите, что здесь есть проблема.
Пример счетчика:
if(isAvailable(ticketId){ //非原子操作
1、给现金增加操作
2、deleteTicketById(ticketId)
}else{
return "没有可用现金券";
}
Чтобы было проще понять, взгляните на эту блок-схему:
- 1. Тема А плюс наличные
- 2. Поток B плюс наличные
- 3. Поток A удаляет флаг заявки
- 4. Поток B удаляет флаг заявки
Очевидно, что таким образом возникает проблема параллелизма, и положительный пример должен использовать атомарность операции удаления базы данных, как показано ниже:
if(deleteAvailableTicketById(ticketId) == 1){ //原子操作
1、给现金增加操作
}else{
return “没有可用现金券”
}
35 Многопоточный асинхронный отдает приоритет соответствующему пулу потоков вместо нового потока и учитывает, изолирован ли пул потоков
Зачем сначала использовать пулы потоков? Каковы преимущества использования пула потоков?
- Это помогает нам управлять потоками и избегать увеличения потребления ресурсов при создании и уничтожении потоков.
- Улучшить отзывчивость.
- повторное использование.
В то же время старайтесь не разделять пул потоков для всех служб, и вам необходимо учитывать изоляцию пула потоков. Он заключается в выделении разных пулов потоков для разных ключевых предприятий, и тогда параметры пула потоков также должны быть рассмотрены соответствующим образом. Я написал несколько пулов потоков раньше, и я думаю, что они неплохи Заинтересованные друзья могут взглянуть на это.
36. Оптимизируйте структуру программы, чтобы свести к минимуму повторные вызовы методов.
Пример счетчика:
public static void listDetail(List<UserInfo> userInfoList) {
for (int i = 0; i < userInfoList.size(); i++) {
//重复调用userList.size()方法了
}
}
Положительный пример:
public static void listDetail(List<UserInfo> userInfoList) {
int length = userInfoList.size();
for (int i = 0; i < length; i++) {
//减少调用userList.size()方法,只在length变量调了一次。
}
}
37. Непосредственно большие файлы или чтение слишком большого количества данных из базы данных в память за один раз могут вызвать проблемы с OOM.
Если в память большого файла или базы данных одновременно попадает слишком много данных, это вызовет OOM. Поэтому, почему обычно рекомендуется запрашивать базу данных БД в пакетном режиме.
При чтении файла файл обычно не слишком велик, поэтому используйте Files.readAllLines(). Зачем? Поскольку он напрямую считывает файл в память, считается, что он не будет использоваться до OOM.Вы можете посмотреть его исходный код:
public static List<String> readAllLines(Path path, Charset cs) throws IOException {
try (BufferedReader reader = newBufferedReader(path, cs)) {
List<String> result = new ArrayList<>();
for (;;) {
String line = reader.readLine();
if (line == null)
break;
result.add(line);
}
return result;
}
}
Если файл слишком большой, вы можете использовать Files.line() для его чтения по запросу.В то время, когда вы читаете файл, вам обычно нужно закрыть поток ресурсов после использования.
38. При вызове стороннего интерфейса необходимо учитывать обработку исключений, безопасность и повторную попытку по тайм-ауту.
В повседневной разработке, если вам часто нужно вызывать сторонние службы или распределенные удаленные службы, вам необходимо учитывать:
- Обработка исключений (например, если вы вызываете чужой интерфейс, если есть исключение, как с ним бороться, следует ли повторить попытку или рассматривать его как сбой)
- Тайм-аут (невозможно оценить, как долго обычно возвращается другой интерфейс, обычно устанавливайте время отключения по тайм-ауту для защиты вашего интерфейса)
- Количество повторных попыток (ваша настройка интерфейса не удалась, вам нужно повторить попытку, вам нужно подумать об этой проблеме с точки зрения бизнеса)
Простой пример: если вы звоните в чужой сервис с помощью http-запроса, вам необходимо рассмотреть возможность установки времени подключения и количества повторных попыток.
39 Не используйте циклические копии коллекций, попробуйте использовать методы, предоставляемые JDK, для копирования коллекций.
- JDK предоставляет собственные методы API, которые могут напрямую указывать емкость коллекции, чтобы избежать потери производительности из-за многократного расширения.
- Нижний уровень этих методов реализуется путем вызова метода System.arraycopy, который более эффективен для пакетного копирования данных.
Пример счетчика:
public List<UserInfo> copyMergeList(List<UserInfo> user1List, List<UserInfo> user2List) {
List<UserInfo> userList = new ArrayList<>(user1List.size() + user2List.size());
for (UserInfo user : user1List) {
userList.add(user);
}
for (UserInfo user : user2List) {
userList.add(user);
}
return user1List;
}
Положительный пример:
public List<UserInfo> copyMergeList(List<UserInfo> user1List, List<UserInfo> user2List) {
List<UserInfo> userList = new ArrayList<>(user1List.size() + user2List.size());
userList.addAll(user1List);
userList.addAll(user2List);
return user1List;
}
40. Для сложной логики кода добавьте понятные комментарии
При написании кода не обязательно писать слишком много комментариев, правильное наименование переменной метода — лучший комментарий. Однако, если это код со сложной бизнес-логикой, писать четкие комментарии действительно необходимо. Четкие комментарии более благоприятны для последующего обслуживания.
41. В случае многопоточности учитывайте линейную безопасность
В случае высокой параллелизма HashMap может иметь бесконечный цикл. Поскольку это нелинейно безопасно, рассмотрите возможность использования ConcurrentHashMap. Так что постарайтесь сделать это привычкой, не придумывайте новый HashMap();
- Hashmap, Arraylist, LinkedList, TreeMap и т. д. линейно небезопасны;
- Vector, Hashtable, ConcurrentHashMap и т. д. линейно безопасны.
42. При использовании функции весенних транзакций обратите внимание на питы, где эти транзакции не вступают в силу
В ежедневном развитии бизнеса мы часто имеем дело с транзакциями, и сбои транзакций в основном включают следующие сценарии:
- Базовый механизм базы данных не поддерживает транзакции
- Использование в непубличных модифицированных методах
- Свойство rollbackFor задано неправильно
- Этот метод класса вызывается напрямую
- Исключение было съедено try...catch, что привело к сбою транзакции.
Пример счетчика:
public class TransactionTest{
public void A(){
//插入一条数据
//调用方法B (本地的类调用,事务失效了)
B();
}
@Transactional
public void B(){
//插入数据
}
}
Метод аннотированной транзакции вызывается непосредственно в методе этого класса, и транзакция не выполняется.
43. Используйте Executors для объявления пула потоков, проблема OOM newFixedThreadPool
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executor.execute(() -> {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//do nothing
}
});
}
IDE указывает параметры JVM: -Xmx8m -Xms8m :
результат операции:
Давайте посмотрим на исходный код: на самом деле newFixedThreadPool использует неограниченную очередь!
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
...
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
...
}
Количество основных потоков в пуле потоков newFixedThreadPool фиксировано, и он использует почти неограниченную очередь блокировки LinkedBlockingQueue. Когда основной поток израсходован, задача будет помещена в очередь блокировки.Если время выполнения задачи относительно велико и не освобождается, все больше и больше задач будет накапливаться в очереди блокировки, и, наконец, использование памяти машина продолжит парить.Вызывает JVM OOM.
44. После перехвата исключения попробуйте не использовать e.printStackTrace(), а использовать журнал для печати.
Пример счетчика:
try{
// do what you want
}catch(Exception e){
e.printStackTrace();
}
Положительный пример:
try{
// do what you want
}catch(Exception e){
log.info("你的程序有异常啦",e);
}
45. Интерфейсы должны учитывать идемпотентность
Интерфейсы следует рассматривать как идемпотенты, особенно такие важные интерфейсы, как захват красных конвертов и перевод денег. Наиболее интуитивный бизнес-сценарий заключается в том, что пользователь щелкает дважды, независимо от того, удерживается ли ваш интерфейс.
Общие идемпотентные технические решения следующие:
- операция запроса
- уникальный индекс
- механизм токенов для предотвращения повторных отправок
- Операции удаления/обновления базы данных
- оптимистическая блокировка
- пессимистический замок
- Распределенные блокировки Redis, Zookeeper (ранее распределенные блокировки Redis использовались для захвата красных конвертов)
- идемпотент конечного автомата
46. Для функций с большим количеством строк рекомендуется разбивать небольшие функции для повышения читабельности.
Пример счетчика:
public class Test {
private String name;
private Vector<Order> orders = new Vector<Order>();
public void printOwing() {
//print banner
System.out.println("****************");
System.out.println("*****customer Owes *****");
System.out.println("****************");
//calculate totalAmount
Enumeration env = orders.elements();
double totalAmount = 0.0;
while (env.hasMoreElements()) {
Order order = (Order) env.nextElement();
totalAmount += order.getAmout();
}
//print details
System.out.println("name:" + name);
System.out.println("amount:" + totalAmount);
}
}
Положительный пример:
public class Test {
private String name;
private Vector<Order> orders = new Vector<Order>();
public void printOwing() {
//print banner
printBanner();
//calculate totalAmount
double totalAmount = getTotalAmount();
//print details
printDetail(totalAmount);
}
void printBanner(){
System.out.println("****************");
System.out.println("*****customer Owes *****");
System.out.println("****************");
}
double getTotalAmount(){
Enumeration env = orders.elements();
double totalAmount = 0.0;
while (env.hasMoreElements()) {
Order order = (Order) env.nextElement();
totalAmount += order.getAmout();
}
return totalAmount;
}
void printDetail(double totalAmount){
System.out.println("name:" + name);
System.out.println("amount:" + totalAmount);
}
}
Чрезмерно многословная функция или фрагмент кода, который требует комментариев, чтобы люди поняли цель, подумайте о том, чтобы разделить его на функциональный блок с четкими функциями и определить четкие и короткие имена функций, что сделает код более элегантным.
47. Обычно рекомендуется иметь несколько журналов для сопровождения вашего ключевого бизнес-кода.
Независимо от того, где находится критически важный бизнес-код, для его сопровождения должно быть достаточно журналов.
Например: вы реализуете трансферный бизнес, переводите несколько миллионов, потом перевод проваливается, потом клиент жалуется, а потом вы не напечатали в журнале, думаете о страшной дилемме, но вам нечего делать. . .
Итак, какая информация журнала вам нужна для вашего трансферного бизнеса? По крайней мере, перед вызовом метода нужно вывести входные параметры, а после вызова интерфейса нужно поймать исключение и распечатать лог, относящийся к исключению, следующим образом:
public void transfer(TransferDTO transferDTO){
log.info("invoke tranfer begin");
//打印入参
log.info("invoke tranfer,paramters:{}",transferDTO);
try {
res= transferService.transfer(transferDTO);
}catch(Exception e){
log.error("transfer fail,cifno:{},account:{}",transferDTO.getCifno(),
transferDTO.getaccount())
log.error("transfer fail,exception:{}",e);
}
log.info("invoke tranfer end");
}
В дополнение к печати достаточного количества журналов, мы также должны обратить внимание на тот факт, что уровень журнала используется неправильно. Не печатайте информационный журнал, но вы печатаете его как уровень ошибки. вам вставать посреди ночи, чтобы решить проблему.
48. Некоторые переменные факторы, такие как красные скины конвертов и т.п., не лучше ли будет сделать их настроенными?
Если продукт запрашивает красный конверт, на Рождество скин красного конверта связан с Рождеством, а во время Праздника Весны — красный скин конверта и т. д.
Пример счетчика:
if(duringChristmas){
img = redPacketChristmasSkin;
}else if(duringSpringFestival){
img = redSpringFestivalSkin;
}
Если во время Фестиваля фонарей у мисс Операции вдруг появится идея, и красный скин конверта изменится на фонарь, в это время вам нужно изменить код и переиздать его? С самого начала внедрить таблицу конфигурации скинов красных конвертов и настроить скины красных конвертов? Чтобы заменить скин красного конверта, просто измените данные таблицы.
49. Перебирать коллекцию, которую нужно использовать, напрямую, без дополнительных операций
Непосредственно перебирать коллекцию, которая будет использоваться, без получения данных с помощью других операций, что обычно представляет собой итеративный обход Map:
Пример счетчика:
Map<Long, UserDO> userMap = ...;
for (Long userId : userMap.keySet()) {
UserDO user = userMap.get(userId);
...
}
Положительный пример:
Map<Long, UserDO> userMap = ...;
for (Map.Entry<Long, UserDO> userEntry : userMap.entrySet()) {
Long userId = userEntry.getKey();
UserDO user = userEntry.getValue();
...
}
50. Шаблон стратегии + фабричный метод для оптимизации избыточного, если еще
Пример счетчика:
String medalType = "guest";
if ("guest".equals(medalType)) {
System.out.println("嘉宾勋章");
} else if ("vip".equals(medalType)) {
System.out.println("会员勋章");
} else if ("guard".equals(medalType)) {
System.out.println("展示守护勋章");
}
...
Сначала мы абстрагируем каждый блок кода условной логики в публичный интерфейс, определяем соответствующий класс реализации стратегии в соответствии с каждым логическим условием, и можно получить следующий код:
//勋章接口
public interface IMedalService {
void showMedal();
}
//守护勋章策略实现类
public class GuardMedalServiceImpl implements IMedalService {
@Override
public void showMedal() {
System.out.println("展示守护勋章");
}
}
//嘉宾勋章策略实现类
public class GuestMedalServiceImpl implements IMedalService {
@Override
public void showMedal() {
System.out.println("嘉宾勋章");
}
}
//VIP勋章策略实现类
public class VipMedalServiceImpl implements IMedalService {
@Override
public void showMedal() {
System.out.println("会员勋章");
}
}
Затем мы определяем класс фабрики стратегий для управления этими медалями и реализуем класс стратегии следующим образом:
//勋章服务工产类
public class MedalServicesFactory {
private static final Map<String, IMedalService> map = new HashMap<>();
static {
map.put("guard", new GuardMedalServiceImpl());
map.put("vip", new VipMedalServiceImpl());
map.put("guest", new GuestMedalServiceImpl());
}
public static IMedalService getMedalService(String medalType) {
return map.get(medalType);
}
}
После оптимизации получилось так:
ublic class Test {
public static void main(String[] args) {
String medalType = "guest";
IMedalService medalService = MedalServicesFactory.getMedalService(medalType);
medalService.showMedal();
}
}
Ссылка и спасибо
- 50 примеров эффективного кода для навыков программирования на Java
- Пишите код с этими идеями, коллеги не подумают, что вы копипастный программист
- Пишите код с этими 16 полезными привычками, которые могут сократить на 80 % ошибок, не связанных с бизнесом.
- 21 яма в ежедневной разработке Java, на сколько вы наступили?
- Восемь решений для оптимизации кода if-else