十倍速开发提效

简介: > *概述:对`jetbrains`系列 IDE 使用技巧进行系统化梳理和整合* ## 背景 ### 目的 欲善其事,先利其器。对于研发同学,在日常的开发工作中,我们与之打交道最多的便是编程的IDE。能否高效和灵活的使用IDE,将对我们的工作效率起着举足轻重的作用。 研发同学在开发中最主要做的两件事分别是架构设计和编码,前者主要取决于大量的项目经验积累和个人的思考深度,也是
概述:对jetbrains系列 IDE 使用技巧进行系统化梳理和整合

背景

目的

欲善其事,先利其器。对于研发同学,在日常的开发工作中,我们与之打交道最多的便是编程的IDE。能否高效和灵活的使用IDE,将对我们的工作效率起着举足轻重的作用。

研发同学在开发中最主要做的两件事分别是架构设计和编码,前者主要取决于大量的项目经验积累和个人的思考深度,也是作为研发的核心竞争力,短时间内很难快速求成;后者主要取决于日常的编码练习和一定程度的IDE信息差,能够通过下文中介绍的一系列技巧进行能力的快速补齐和巩固加强。

本文的主要目的有两方面

一方面,对IDE的快捷操作和高效技巧,结合自己多年的实践和理解,进行一次系统性的总结和梳理,完善自己这部分的知识体系

另一方面,希望通过本文系统性的梳理,能够帮助更多的同学提高研发效率,无论你是刚入手不久的新人,还是有着多年开发经验的专家,相信你都能够在本文中发现一片新天地,让你能够有更多的时间和精力去做更有意义的事情。

定位

网上很多技术网站和个人博客,对于IDE各种技巧和便捷操作总结得非常具体且详细,对于单点的详尽程度都是极具参考和学习价值的。但其对应的问题是,这些很多很优秀的文章,出自于不同的手笔,有各自的行文风格,且分散在各个网站的散点,难以系统化。

我对本文的定位是,将各种技巧以大分类的形式进行收拢和聚合,以帮助大家构建和完善整体的知识体系,大幅度提高开发效率。对于每个分类点到即止,替代咀嚼式灌输方式的是,尽量使用渐进式引导的方式。

普适性

jetbrains系列的IDE产品众多,除了下图之外,还有其他未列入的,如Google二次开发的Android Studio等。虽然归为多个产品实例,但这些IDE的内核都是一样的,只是在内核的基础上额外添加了各自的语言特性。本文将以使用量最高的一款IDE——IDEA为例进行展开,文中提到的绝大多数能力和技巧,在其他IDE均同样适用,一通则百通

image.png

效果图

在开始了解之前,先来整体看一下编码过程中一些高效操作的效果图。

以下是基于Keymap的操作

keymap.gif

以下是基于postfix的操作

postfix.gif

看完效果图,接下来正式进入各模块的介绍环节。

Postfix Completion

介绍

Postfix Completion (下称Postfix) 是一种通过. + 模板Key来对当前已经输出的表达式,添加和应用预设代码模板的编码增强能力。

其核心要解决的问题是,将编码过程中一些通用的代码结构范式进行抽象和沉淀,并能在同类型的场景下,通过. + 模板Key的方式进行唤醒和复用。

举个例子,现在需要完成下面一段代码的编写,为了对name参数进行判空保护

if (name != null) {
  
}
  • 在普通文本编辑器中,其中if 2次,name 4次,(){}!= 共6次,再加空格Tab和光标切换,一共需要按键23
  • 在IDEA编辑器中,不使用Postfix时,一共需要按键20次,不考虑代码格式化的情况可以减少到16
  • 在IDEA编辑器中,使用Postfix时,只需要8次,如下图

    postfix_nn.gif

在这个例子中,可以对比出使用Postfix前后的效果,使用之后在编码中减少了一半的手动按键操作,且生成的代码是自带格式化的。在实际的编码过程中,各项目大小和复杂度差异性虽然很大,但细化到这种基本单位的编程范式时,它们都是融会贯通的。

与上例中nn并列的Postfix,IDEA给我们预设的还有很多,下面对一些非常高频使用的Postfix进行梳理

梳理

var

快速定义一个局部变量,自带IDE的类型推断

postfix_var.gif

notnull

快速进行NPE的判空保护

postfix_notnull.gif

nn

notnull,是它的简写,推荐用这个,更加便捷

postfix_nn.gif

try catch

快速对当前语句添加try catch异常捕获,同时IDE还会对catch中的Exception自动做类型推断

postfix_try.gif

cast

快速实现类型强转,不需要反复使用()包裹和光标切换;配合instanceof使用时还能自动实现cast类型的推断

postfix_cast.gif

if

快速实现if判断的代码范式

postfix_if.gif

throw

快速实现抛异常

postfix_throw.gif

for

快速实现集合或数组的迭代

postfix_for.gif

fori

快速实现集合或数组的带索引值迭代;同时对整型数字也支持

postfix_fori.gif

sout/soutv

快速实现(不带参数/带参数)的打印功能

postfix_sout.gif

return

快速实现方法中的值返回逻辑

postfix_return.gif

format

快速实现字符串格式化

postfix_format.gif

高级用法

担心系统预设的Postfix不足以满足我们的编码需求,IDEA还提供了Postfix的自定义功能。

这里我以自定义一个对集合判空的代码范式,来举例说明自定义Postfix的流程

  • 进入IDE设置界面,然后依次进入Editor => General => Postfix Completion => 面板左下角加号 => Java

    postfix_add1.jpg

  • 在弹起的页面中,按照下图进行配置,然后保存退出设置页

    postfix_add2.jpg

此时我们自定义的isempty这个Postfix即完成了,下面来看下实际使用的效果

postfix_custom.gif

在实际开发过程中,对于根据已经输入的表达式就能决定接下来代码格式的功能,我们都能使用这种自定义方式进行代码的抽象和复用

接下来介绍IDE中一种跟Postfix功能很相像,但灵活度更高的能力——Live Template。

Live Template

介绍

介绍之前可以先看一段简短的编码过程

live_template_1.gif

上面这段编码中,我先后使用了Live Template的以下三个模板能力

  • psfs:定义字符串常量
  • main:添加入口函数
  • sout:实现日志输出

这里我们将其和上面提到的Postfix对比来看,两者都是提供代码级别模板的功能。不同的是,Postfix需要一个已经输入的表达式和. + 模板Key来进行触发,而Live Template不需要这些,它仅仅需要模板Key即可触发。

Live Template提供的预设模板要比Postfix要高出一个数量级,因此这里我就不进行一一演示,我们可以进行设置面板,然后按照Editor => Live Templates的路径自行查看,如下图

image.png

高级用法

和Postfix一样,Live Template也支持自定义模板,但它的自定义模板相对来说更加灵活和开放,甚至支持我们直接植入脚本。鉴于Live Template的高度灵活性,单独介绍这块会占据大量的篇幅,因此这里我将从几个实际的案例场景来开拓一下思路,而具体自定义拓展过程就不详细展开介绍了。

Key值映射

将DB中查询到List<T>结构的数据,根据Key值映射转化为Map<K, T>结构的数据,以便于进行后续的数据填充逻辑

live_template_list2Map.gif

DB批量查询

在数据查询时,我们会有根据ID主键进行批量DB数据查询的诉求,如下

List<User> users = userMapper.queryUserByIds(userIds);

这种写法会有一个弊端,就是当userIds大到一定的量级时,该查询会变得非常耗时。

对于该问题其中一个解法是,将这个大的userIds拆分成多个批次,然后让这多个批次异步并行去查询。这里便使用Live Template来抽取一个针对该场景的代码模板,如下

live_template_batchQuery.gif

按照该模板,我们的查询语句将变成这样

List<User> users = batchQuery(userIds, 100, userMapper::queryUserByIds, null);

可以看到,和之前相比,多传一个分批的size参数,同时还支持指定的异步任务调度器的自定义配置,而返回结果和之前的查询方式保持完全一致,不需要外部有额外的适配工作。

脚本植入

这个功能是我非常看好Live Template的主要原因,它的灵活性和拓展性也主要来源于这里。它支持我们通过一个模板Key来唤起和执行一段脚本,这也就意味着,我们的自定义的Live Template模板是可编程的,极大程度提高了该模板的拓展性。

单描述功能会有些空洞,这里我结合一个实际案例进行介绍,我们来实现一个跨电脑的代码共享功能:

  • 首先,使用python的flask框架写一个极简的服务端应用并启动,提供最简单的pushpull的能力,如下

    from flask import Flask, request
    
    DEFAULT = 'nothing'
    code = DEFAULT
    
    app = Flask(__name__)
    
    @app.route('/push')
    def push():
        global code
        code = request.args.get('code', DEFAULT)
        return 'Success'
    
    @app.route('/pull')
    def pull():
        return code
    
    app.run()
  • 然后,我们来实现一个代码pull的模板,这里应用了Live Template的groovy script能力,对应脚本如下

    def url = new URL('http://127.0.0.1:5000/pull');
    def conn = url.openConnection() as HttpURLConnection;
    def result = conn.inputStream.text;
    return result
  • 最后,再实现代码push的模板,脚本如下(下面的代码入参,是通过剪切板赋值传递过来的)

    def code = _1;
    def url = new URL('http://127.0.0.1:5000/push?code=' + new URLEncoder().encode(code));
    def conn = url.openConnection() as HttpURLConnection;
    def result = conn.inputStream.text;
    return result

此时就已经完成了跨设备的代码分享功能,为方便演示,这里就用People1People2两个类来模拟两台独立的电脑。People1将自己的一段代码复制到剪切板中,然后通过push来将这段代码通过push接口上传到python服务应用中;People2通过pull来调用服务端的pull接口,访问到People1上传的代码并输入到当前的代码编辑器中。

live_template_shareCode.gif

这里的代码共享只是一个引子,除此之外,我们还能写很多有意思的脚本,比如在IDE中查天气、通过IDE聊天等等,自行脑补拓展。

介绍完Live Template之后,接下来介绍文件级别的模板——File Template。

File Template

介绍

File Template,顾名思义,对应文件级别的模板。对于该模板,我们使用脚本的主要在于两个场景,分别是文件头和文件的自定义,下面结合案例依次展开。

自定义文件头

按照下图的路径,来更改文件头的格式,IDE就会在我们新建一个类或接口时,根据这里的配置格式来自动生成对应的文件注释头。

image.png

抽象通用Controller

看下面一段代码,这是一个针对于User这个domain的增删改查接口类

package com.alibaba.ide.code.controller;

import com.alibaba.ide.code.entity.Result;
import com.alibaba.ide.code.entity.User;
import com.alibaba.ide.code.service.Condition;
import com.alibaba.ide.code.service.UserService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;

/**
 * @author puke
 * @version 2021/2/9
 */
@RestController
@RequestMapping("api/user")
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping
    public Result<User> create(@RequestBody User record) {
        User user = userService.insert(record);
        return Result.success(user);
    }

    @PutMapping
    public Result<User> update(@RequestBody User record) {
        User user = userService.update(record);
        return Result.success(user);
    }

    @DeleteMapping("{id}")
    public Result<Void> deleteById(@PathVariable Serializable id) {
        boolean success = userService.deleteById(id);
        return success ? Result.success() : Result.fail();
    }

    @GetMapping("{id}")
    public Result<User> queryById(@PathVariable Serializable id) {
        User user = userService.queryById(id);
        return Result.success(user);
    }

    @GetMapping
    public Result<List<User>> queryByCondition(Condition<User> condition) {
        List<User> list = userService.queryByCondition(condition);
        return Result.success(list);
    }
}

仔细看这段代码会发现,如果基于该接口再新增另一个domain对应的Controller接口类,代码中的基本结构和逻辑都是可以复用的。此时,便是File Template排上用场的地方,我们定义一个通用的Controller模板,将共性的部分抽象到模板里,再将差异性的部分通过模板入参Subject变量传入进来。(注,这里需要用到Velocity 模板的知识。)

#set($SubjectOfLowerFirst = ${Subject.substring(0,1).toLowerCase()} + $Subject.substring(1))
package ${PACKAGE_NAME};

import com.alibaba.ide.code.entity.Result;
import com.alibaba.ide.code.entity.${Subject};
import com.alibaba.ide.code.service.Condition;
import com.alibaba.ide.code.service.${Subject}Service;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;

#parse("File Header.java")
@RestController
@RequestMapping("api/${SubjectOfLowerFirst}")
public class ${Subject}Controller {

    @Resource
    private ${Subject}Service ${SubjectOfLowerFirst}Service;

    @PostMapping
    public Result<${Subject}> create(@RequestBody ${Subject} record) {
        ${Subject} ${SubjectOfLowerFirst} = ${SubjectOfLowerFirst}Service.insert(record);
        return Result.success(${SubjectOfLowerFirst});
    }

    @PutMapping
    public Result<${Subject}> update(@RequestBody ${Subject} record) {
        ${Subject} ${SubjectOfLowerFirst} = ${SubjectOfLowerFirst}Service.update(record);
        return Result.success(${SubjectOfLowerFirst});
    }

    @DeleteMapping("{id}")
    public Result<Void> deleteById(@PathVariable Serializable id) {
        boolean success = ${SubjectOfLowerFirst}Service.deleteById(id);
        return success ? Result.success() : Result.fail();
    }

    @GetMapping("{id}")
    public Result<${Subject}> queryById(@PathVariable Serializable id) {
        ${Subject} ${SubjectOfLowerFirst} = ${SubjectOfLowerFirst}Service.queryById(id);
        return Result.success(${SubjectOfLowerFirst});
    }

    @GetMapping
    public Result<List<${Subject}>> queryByCondition(Condition<${Subject}> condition) {
        List<${Subject}> list = ${SubjectOfLowerFirst}Service.queryByCondition(condition);
        return Result.success(list);
    }
}

模板定义完成,接下来来看一下实际的使用效果

file_template_controller.gif
这里使用Goods作为新的domain对象,可以看到,生成的Controller代码已经具备UserController的全部能力,并且生成的代码全部都是Goods相关的api,这样就实现了File Template的横向迁移能力。

低频高效快捷键

介绍

IDEA中的快捷键多达上百个,我们很难把每个都记清楚,网上也有很多对应的总结。这里我主要梳理一些,大家使用相对比较低频,但又非常高效的快捷键。

梳理

选择重复元素 Control + G

keymap_next.gif

批量框选 Option + 鼠标左键拖拽

keymap_option.gif

整行移动 Option + Shift + ↑/↓

keymap_move_line.gif

整行/块复制 Command + D

keymap_copy_line.gif

展开/收起 Command + . or Command + Shift + +/-

keymap_expand_shrink.gif

修改方法签名 Command + F6

keymap_modify_method.gif

查看历史剪切板 Command + Shift + V

keymap_clipboard.gif

代码抽取

抽局部变量 Command + Option + V

keymap_extract_v.gif

抽成员变量 Command + Option + F

keymap_extract_f.gif

抽静态常量 Command + Option + C

keymap_extract_c.gif

抽方法入参 Command + Option + P

keymap_extract_p.gif

抽方法 Command + Option + M

keymap_extract_m.gif

代码调试

代码调试在开发中使用的非常多,常规的单步、多步、进入、跳出操作这里也不特殊说明了。

有一点值得说的就是,利用条件断点来实现运行期的代码植入功能,先看下图

image.png

可以看到,Debug模式运行时,我们能动态改变age变量的值,本来被赋值为20的,结果输出出来却是10

这个是我在开发中无意间发现的一个功能,算是一个Trick了。但这个功能在实际的开发过程中特别有用,尤其针对于一些代码改动后再次运行的成本比较高的场景。比如Android开发过程中,能够在不重新打整包的情况下,动态修改页面中各个元素的样式、接口的请求、数据的内容等等;再比如服务端场景中,如果我们的应用支持Debug模式,则可以通过该功能实现应用无需重新部署的情况下,进行动态更改上下文逻辑的操作。

写在最后

跬步至千里,小流成江海,开发工作有大小,业务需求有缓急,但终究要落到眼下,从一砖一瓦的基石开始,从一行一列的编码开始,希望本文中能帮助到更多的研发同学。

目录
相关文章
|
2月前
|
安全 数据可视化 数据挖掘
人力系统不知如何选型?看好这些关键指标
在现代企业管理中,选择合适的人力系统至关重要。本文探讨了选型的关键指标,如功能全面性、用户体验、数据分析能力、系统集成性和安全性,并推荐了利唐i人事作为专业解决方案,涵盖招聘、绩效、薪酬及培训管理,助力企业提升管理效率和竞争力。
|
7月前
|
弹性计算 算法 测试技术
芯片研发设计单月算力成本降了50%怎么做到的?
面对芯片设计中日益严峻的算力挑战,S半导体经历了从自建HPC集群到结合公有云的尝试,导致成本上升。后来,采用MMCloud解决方案,首月即实现50%的算力成本降低。MMCloud通过大规模集群管理、算力碎片整理和机型动态调整实现精细化调度,帮助S半导体提升了研发效率并降低成本,展示了在行业寒冬中的降本增效策略。
123 5
|
新零售 人工智能 小程序
案例酷 | “降低变美成本”:Fresh与阿里云联手了
编者按: 一直以来,中国美妆零售都在不断进行着数字化转型,但归根结底,这仍然是一个靠实体店运作、需要与消费者交互、以体验为重要价值诉求的行业。大数据、人工智能等技术的革新,使线上、线下的服务与体验在新零售时代进行了深度融合,让消费者不再受时间和空间的限制。
147 0
|
消息中间件 SQL 运维
开发之痛:稳定的测试环境,怎么就那么难 | 研发效能提升36计
开发之痛:稳定的测试环境,怎么就那么难。对于生产环境,准确、稳定最重要,我们推荐以应用为中心的基于OAM和IaC的实践方式;对于测试环境,隔离、低成本和稳定的依赖是最重要的,我们推荐基于稳定环境的隔离测试环境的实践,复用稳定环境,通过流量隔离和数据隔离来生成测试环境。通过环境建设,我们解决了研发过程中的资源冲突。
1442 1
开发之痛:稳定的测试环境,怎么就那么难 | 研发效能提升36计
|
运维 安全 云计算
新车研发年平均节省9000万!阿里云助力上汽新车研发提速降本
9月15日,记者获悉,自2018年上汽乘用车引入阿里云仿真计算,年平均节省9000万新车研发费用;同时,过去一款新车研发需要3.5到4年,依托阿里云成熟的算力,研发周期平均缩短至2.5到3年。新车研发提速降本,将给车主带来更多便利。
新车研发年平均节省9000万!阿里云助力上汽新车研发提速降本
|
新零售 NoSQL 容灾
业务爆发保持丝般顺滑 阿里云助力完美日记半年内系统吞吐量提升50倍
近年来,完美日记的“小黑钻口红”“动物眼影盘”等爆款彩妆出现在了越来越多女孩子的化妆台上,完美日记(Perfect Diary)是由逸仙电商在2017年推出的彩妆品牌,凭借着高颜值和性价比,完美日记彩妆销量增长迅猛,被众多网友誉为国货之光。
820 0
|
算法 搜索推荐 机器学习/深度学习
端智能揭秘|促使双十一GMV大幅提升,手淘用了什么秘密武器?
信息流作为手淘的一大流量入口,对手淘的浏览效率转化和流量分发起到至关重要的作用。在探索如何给用户推荐其喜欢的商品这条路上,我们首次将端计算大规模应用在手淘客户端,通过端侧丰富的用户特征数据和触发点,利用机器学习和深度神经网络,在端侧持续感知用户意图,抓住用户转瞬即逝的兴趣点,并给予用户及时的结果反馈。通过大半年的不断改进,手淘信息流端上智能推荐在9月中旬全量,并在双十一当天对信息流的点击量和GMV都带来了大幅的提升。下文将给大家分享我们在探索过程中发现的问题,对其的思考和解决方案。
2084 0
端智能揭秘|促使双十一GMV大幅提升,手淘用了什么秘密武器?
|
边缘计算 监控 安全
大直播时代,P2P才是降低成本的必杀技
在流媒体传输分发领域,CDN和P2P一直是经常被拿来进行对比和讨论的一大热点,虽然不少大型视频企业目前同时使用着CDN和P2P两套分发机制,但相对于CDN,很多人对于P2P技术知之甚少。
2500 0