【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
相信在平常编程过程中,给函数或变量起名称是一件令人头痛的事情。如果起的名称不能表达代码表达的逻辑,则别人看此代码会比较难受了。代码首先要给人看,其次才是机器运行。所以给函数或变量命名就非常重要了。 本篇文章主要介绍在Java(Java web)开发过程中给类,函数等的命名。在给函数或者变量命名是一个首要原则就是要尽力的表达代码想要表达的意思。下面会给出一些具体的建议
给类命名
Java中给类命名要遵循首字母大小,驼峰式命名。类名要表达具体的业务对象。在Web开发过程,经常需要建立VO,DTO,BO,Entity等对象(这些对象的含义可以参考阿里巴巴编程规范,里面有具体的介绍)。在给这些领域对象命名时,最好带上VO,DTO等后缀。
函数命令
在给函数命名时,取一个动词很关键,因为一般函数就是执行一个具体的逻辑。在《代码整洁之道》这本书中,作者告诉我们一个函数最好只做一件事情,不要让函数变得很复杂,如果函数复杂,那么如果想要重构就会变得比较困难。同时函数最好不要对于三个参数。
下面是一些常用的动词以及表达的含义,在平常的编程中可以尽量使用下面这些动词
动词选取
类别 单词
添加/插入/插入/创建/初始化/加载 | add/append/insert/create/initialize/load 删除/销毁 | delete, remove, destroy, drop |
---|
打开/开始/启动 | open,start | 关闭/停止 | close, stop | 获取、读取、查找、查询 | get,fetch,acquire,read,search,find,query | 设置,重置,放入,写入,释放,刷新 | set,reset,put,write,release,refresh | 发送,推送 | send,push | 接收, 拉取 | receive,pull | 提交,撤销,取消 | submit,cancel | 收集,采集,选取,选择 | collect,pick,select | 提取,解析 | sub,extract, parse | 编码,解码 | encode,decode | 填充,打包,压缩 | fill,pack,compress | 清空,拆包,解压 | flush,clear,unpack,decompress | 增加,减少 | increase,decrease, reduce 分隔,拼接 | 过滤,校验,检测 split,join,concat | filter, valid,check | 动词决定了函数的动作,名词描述了函数具体操作的对象,名词的选取也要选择常用的,不要选择生僻的。 比如:capacity表示集合容量,size表示实际元素个数,length表示字符串长度。不要用size描述字符串长度 再比如,建造者模式用build做函数名,不要用create。 名词表 类别 单词 | 容量,大小,长度 | capacity,size,length 实例,上下文 | instance,context |
---|
配置 | config,settings | 头部,前面,前一个,第一个 | header,front,previous, first | 尾部,后面,后一个,最后一个 | tail,back,next,last | 区间,区域,某一部分,范围,规模 | range,interval,region,area,section,scope,scale | 缓存,缓冲,会话 | cache,buffer,session | 本地,局部,全局 | local,global | 成员,元素 | member,element 菜单,列表 | 源,目标 menu, list | source,destination,target | 处理采用上面的动词以及名词,还有其他一些注意的地方,如下 多条件查询的函数名词谨慎使用介词by 如下命名 public List getByName(String name) 如果某天需要增加一个查询条件,需要增加电话号码的查询条件,则可能此函数命名方式变成 public List getByNameAndMobile(String name, String mobile); 如果以后还要增加查询条件,则会变成噩梦,比较好的方式是将查询条件分装成查询的领域对象,如下: public List getStudents(StudentCondition searchParam); 返回布尔值的方法尽量以is或are开头 比如 isButtonEnabled 或 areButtonEnable get使用要合理 get表示获取,似乎有返回值的函数都可以用这个动词。但是get比较适合描述获取某个属性,比如POJO(领域对象)中的get方法。但是如果是其他的,建议采用其他动词。比如从服务器获取用户列表,用 fetchUsers ; 获取某个返回的质数, caculatePrime 不要包含函数参数的信息 如用一个 id 和 token 找用户的方法,应该叫 findUser(userId, token) 而不是 findUserByUserIdAndToken(userId, token) 。 这个跟谨慎使用介词by有些类型 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 第一季-思想篇 [ ] 第1期 个人性格 [ ] 第2期 求知欲 [ ] 第3期 人和领导力对拓展性的影响 [ ] 第4期 个人的经验 [ ] 第5期 可劲学开源佩格 [ ] 第6期 反障碍 [ ] 第7期 双活工程师 [x] 第8期 为圆满的人生作准备 [ ] 第9期 时间管理 [ ] 第10期 硬核之哈希函数 [ ] 第11期 硬核之复盘管理 [ ] 第12期 硬核之十年自学编程 Hi,大家好!我是Lucky。今天给小伙伴播报来自一本好书中其中一个章节--为圆满的人生作准备。 我们每个人都守着一扇自内开启的“改变之门”。除了自己,没有人能为你开门,只要你愿意敞开心灵,抛却旧有观念,把良好准则化为习惯,成功圆满就在掌握之中。 人的品德基本上是由习惯组成的。俗谚说: 思想决定行动,行动决定习惯,习惯决定品德,品德决定命运。 习惯对我们的生活有绝大的影响,因为它是一贯的。在不知不觉中,经年累月影响着我们的品德,暴露出我们的本性,左右着我们的成败。教育家曼恩(HoraceMann)曾说:“习惯就仿佛一条缆绳,我们每日为它缠上一股新索,不要多久就会变得牢不可破。”这句话的后半段我不敢苟同,我相信习惯可以养成,也可以打破。只是绝非一蹴而就,而是需要长期的努力及无比的毅力。 习惯是具有极大的引力,只是许多人不加注意或不肯承认罢了。要革除因循苟且、缺乏耐心、吹毛求疵或自私自利等不良习性,缺乏意志力,不能大刀阔斧的改革,便难以竟全功。 向旧习惯说再见 本书将习惯定义为“知识"、“技巧”与“欲望”三者的混合体。知识是理论性的观念,指点我们“做什么”及“为何做”。技巧是指“如何做”。欲望则是“想做”,表示我们有付诸行动的愿望。要培养一种习惯,这三项要素缺一不可。 前面曾提到,要提升自我必须先从观念着手。观念一旦改变,对外界的看法自然不同,而且又会回过头来影响自我,形成一个良性循环。所以我们应该从知识、技巧与欲望三方面努力,突破旧有观念的束缚,使个人与人际关系都能更上一层楼。 改变习惯的过程可能很不好受,毕竟习以为常的事务比较予人安全感。但为追求一生的幸福与成功,暂时牺牲眼前的安适或近利,也是值得的。经过一番努力与牺牲所换来的果实,将更为甜美。 依赖→独立→互赖 所谓成长的三个层次,分别为依赖、独立、互赖。 依赖:围绕着“你”这个观念 你照顾我;你为我的成败得失负责;事情若有差错,我便怪罪于你。 独立:着眼于“我”的观念 我可以自立,我为自己负责,我可以自由选择。 互赖:从“我们”的观念出发 我们可以自主、合作、集思广益,共同开创伟大前程。 产品与产能的跷跷板定理 对“效率”所下的定义是一“产品与产能必须平衡”。伊索寓言中有则鹅生金蛋的故事,正足以说明这个常遭人忽视的原则。 这则故事是说,一个农夫无意间发现一只会生金蛋的鹅,不久便成了富翁。可是财富却使他变得更贪婪更急躁,每天一个金蛋已无法满足他,于是农夫异想天开的把鹅宰杀,企图将鹅肚子里的金蛋全部取出来。谁知打开一看,鹅腹里并没有金蛋,鹅却死了,再也生不出金蛋。 这则寓言是效率观念一个很好的例证。一般人往往自金蛋的角度来衡量效率,也就是产品愈多,效率愈高。可是上面的故事却告诉我们,效率包括两个要素,一是产品(金蛋),也就是你希望获得的结果;一是产能(鹅),也就是你借以达到目标的资产或本领。 仅重视金蛋,无视于鹅的人,结果会连产金蛋的资产本身都保不住。反之,“重鹅轻蛋”的人,最后可能养不活自己,更不用说鹅了。因此,产品与产能必须平衡,才能达到真正的效率。 个人的效率观 略 团队的效率观 略 好了,现在就读到这里。小伙伴们应该想知道这本书吧。普京建议俄罗斯公民阅读这本书《成功人士的七个习惯》,它改变人的思维方式和行为方式。作者是史蒂芬‧柯维《经济学人》杂志推举为最具前瞻的管理思想家。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 1.在线流程图 https://www.processon.com/diagrams 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 仍在整理中,敬请期待。。。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 前言 在还没毕业的去面试的时候,被问到动态代理与策略模式有什么区别,当时想了想也没想出来什么合适的回答,最会草草越过,之后面试完毕进行了相关的查阅,还是懵懵懂懂的,到现在debug SpringMVC源码的时候,遇到 initHandlerAdapters 初始化的后,一直纠结此处是使用了责任链模式还是策略模式,然后就想到了之前遇到的动态代理和装饰者模式。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> The WordPress , Rewrite API is like a mythical creature. Not many know it exists, and few still know how to tame it. In this article, we’ll cover the basics of the Rewrite API and how to make it work for you. The Rewrite API includes three commonly used functions (and a fourth function that doesn’t get much love). add_rewrite_rule add_rewrite_tag add_rewrite_endpoint add_feed – Not incredibly useful, generally, but it could be! How WordPress Permalinks Work When you request a page on a WordPress site, assuming it’s using pretty permalinks, the rules in htaccess file (or webconfig, if you’re into windows) first test to see if the request file and or directory exists on the server. In other words, if someone request yoursite.com/some-folder/ and you’ve created some-folder in the root of your site (via FTP or whatever else), Apache will serve that directory. Same deal for static files. NginX works a similar way, but does so with a try_files directive: try_files $uri $uri/ index.php . If the folder or file doesn’t exist, the server directs the request to index.php, which is, of course, where the magic happens. From here WordPress loads . During this process, WordPress tries to match the request’s url (the stuff after yourdomain.com ) with a series of rewrite rules , which are just regular expressions . If it finds a match, WP will translate the URI into a database query, render the correct template file and serve up the page. When you add a rewrite rule, you’re augmenting the built in rewrite rules with your own. There’s all kinds of stuff you can do with this. Like creating custom shortlinks or giving your WordPress site an API. Adding a Rewrite Rule add_rewrite_rule is the thing to add your own custom rules. It tags three arguments: the regex, the index.php + query string URL you to rewrite the regex matching URI to, and the priority. Generally $priority is always set to “top” to push your rewrite rules before the built in WP rules. Here’s an example. We’re going to rewrite yoursite.com/p/some_number to a post that has the ID, some_number. We’ll hook into init to add the rule. The first argument says, “match p followed by a slash, then a series of one or more numbers (and maybe another slash).” The second argument tells wordpress to rewrite the rule to index.php?p=the_group_of_numbers. $matches[1] refers to the first group of parenthesis inside the rewrite regex (the first argument). Before this will work for you, however, you’ll need to flush your rewrite rules. You can do this pro grammatically, with an activation hook in a plugin, or manually by visiting the Settings > Permalinks page in your WordPress admin and hitting save. Here’s how to do it with an activation hook: The reason the above rewrite rule works is because WordPress recognizes what the p query string key means — it knows to make it to a post id. What if you want to create something like a URL endpoint for a form submission? None of the built in query variables would do for this, so we need to role our own. Here’s the rewrite rule: If you flush your rewrite rules and visit yoursite.com/submit/ , it’s going to show the blog/index page. WordPress doesn’t know what to do with the form query variable. And if you try to grab the variable with get_query_var , it’s not going to work. Why? WP did not recognize the the form variable, so it stripped it out. There’s one of two ways you can add a query variable. We’ll cover one here, and one in the next section of this article. To add the variable you add a filter to query_vars and push form onto the query variable array. Catching Custom Query Variables You can get a query variable with the get_query_var function. Like this. get_query_var can be used any time after the query is set up — any time after init . It’s safe to use inside a template, in other words. This is a fairly common practice for me: hook into template redirect, catch a custom query variable, do stuff inside the WordPress environment, then exit(); to stop the theme from loading. Adding Rewrite Tags add_rewrite_tag lets you add custom tags similar to %postname% or any of the permalink structure tags . It can be used in place of filtering query_vars . In other words, instead of this: Is a complete unit. No need to filter query_vars to make sure WordPress recognizes the form variable. That said, is this the right approach? Rewrite tags are more helpful if you’re going to use them as part of, say, a custom permlink structure for a custom post type. In our example, they probalby aren’t necessary. add_rewrite_tag is also misleading. It sort of implies that, with registration, you somehow tell WordPress to know how to replace the tag with the correct value. WP does not do that. You’ll have to do that manually, but it is possible . Adding Rewrite Endpoints Rewrite endpoints are things that get tacked out the end of URLs on your site. Trackbacks are a good example: any post on a WordPress side has the endpoint /trackback/ that alters the behavior of the page to, you guessed it, add pingbacks/trackbacks. We’ll add an endpoint to posts to create an “api”. Whenever you visit yoursite.com/some-permalink/json/ WP will spit out a nice json version of the post. add_rewrite_endpoint tags two arguments: (1) the endpoint itself and (2) where you want the endpoint to live ( $place ). $place is going to be one of the many EP_* constants (view them here ). If you want to place an endpoind in more than one place, you can combine EP constants with the bitwise OR (the pipe: |). Like our other rewrites, we’ll hook into init to add our endpoint. add_rewrite_endpoint takes care of adding the rewrite rules and adding the query variable for us! Here’s the code we’ll use to catch our /json/ endpoint. If you visit yoursite.com/some-permalink/json/ (after flushing rewrite rules, of course) it’s not going to work, but yoursite.com/some-permalink/json/asdf will! That’s because add_rewrite_endpoint is going to set the json query variable equal to the stuff that comes after the /json/ endpoint. If it’s an empty string (as it is with yoursite.com/some-permalink/json/ ) php will evaluate it as false. if( is_singular() && get_query_var( ‘json’ ) !== false ) won’t work either as it will stop all permalinks from loading. Instead, we need to hook into request and give our json query variable a value if it’s set. Now if you visit yoursite.com/some-permalink/json/ , it will work. Next up we just need to enhance our handler function (hooked into template_redirect a bit). $post->post_title, 'content' => $post->post_content ); header('Content-Type: text/plain'); echo json_encode( $out ); exit(); } } Now if you visit yoursite.com/some-permalink/json/ a nice JSON will be there for consumption. Adding Custom Feeds The little used (or talked about) add_feed function is used to — well, to add feeds to wordpress. Like yoursite.com/feed/rss . add_feed takes to arguments: the feed name and the function you wish to fire when the feed is loaded. Like our other rewrite, writes, we’ll hook into init . Since we’re into JSON in this article, let’s make a feed that displays our list of posts as a JSON string. The function pmg_rewrite_json_feed is going to receive one argument: whether or not this is a comments feed. Since we don’t really need this, we’ll leave it out. Our hooked function is what you’d expect: get some posts, turn them into a JSON. $p->post_title, 'content' => $p->post_content ); } header('Content-Type: text/plain'); echo json_encode( $out ); } Demystifying the WordPress Rewrite API Hopefully this article cleared some things up for you regard the rewrite API. It’s very powerful, and allows you to use WordPress in ways that are well outside its usual scope. All of this code is available here . 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 公众号带参数的二维码,通过公众号接口开发,实现公众号粉丝统计,粉丝批量打标签分组或修改备注名,关注取关数据都能统计到,数据每天实时更新。会编程的都可以实现效果,也可以直接用微号帮平台渠道二维码生成实现。 1、渠道二维码生成 渠道二维码生成功能,微号帮支持自定义扫码回复、粉丝打标签分组、修改备注名,粉丝关注数据统计。 2、粉丝关注统计数据 通过带参数的二维码关注的粉丝都能统计到,数据每天更新,如果设置了打标签分组,还可以查看分组的粉丝明细。 3、不同二维码回复不同消息 微号帮平台通过带参数的二维码,每个二维码为一个渠道,每个二维码支持不同的消息类型设置,文本信息、图文消息、卡券、小程序、图片、音频、视频都可以设置成扫码回复消息。 4、带参数的二维码设置 关注二维码支持公众号粉丝打标签分组或修改备注名,默认渠道名称即标签分组,也可以修改成别标签分组。
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 一、了解Java NIO NIO(Non-Blocking I/O,java中,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,是解决高并发、I/O处理问题的有效方式。 1. 传统的BIO BIO(Blocking I/O)即同步阻塞I/O,在NIO出现之前主要使用BIO及新建线程的方式来解决并发请求,但这样很容易因线程瓶颈而造成限制。下面是BIO的经典编程模型(主要代码): { ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池 ServerSocket serverSocket = new ServerSocket(); serverSocket.bind(8088); while(!Thread.currentThread.isInturrupted()){//当前线程未中断 Socket socket = serverSocket.accept(); executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程 } class ConnectIOnHandler extends Thread{ private Socket socket; public ConnectIOnHandler(Socket socket){ this.socket = socket; } public void run(){ while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){死循环处理读写事件 String someThing = socket.read()....//读取数据 if(someThing!=null){ ......//处理数据 socket.write()....//写数据 } } } } 之所已使用多线程,因为accept()、read()、write()三个函数都是同步阻塞的,当一个连接存在的时候,系统是阻塞的,所以利用多线程让cpu处理更多的申请。多线程的本质: 利用cpu的多核特性 当I/O阻塞系统,但cpu空闲的时候,可以利用多线程使用cpu资源 现在的多线程一般都使用线程池,可以让线程的创建和回收成本相对较低。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但当处理百万级的连接时,使用这种模型肯定是不实际的,让cpu去创建这么多的线程是不可能的。 2. 优秀的NIO ‘优秀’是当今很流行的一个词,可以十分恰当的形容NIO在java中的重要性。从JDK1.4开始,Java提供了一系列改进的输入/输出处理的新特性,被统称为NIO(即New I/O)。新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能。 NIO采用内存映射文件的方式来处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件了。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同, NIO支持面向缓冲区(Buffer)的、基于通道(Channel)的IO操作。NIO将以更加高效的方式进行文件的读写操作。 NIO中的三个重要组件: 2. 1 缓冲区Buffer 缓冲区有直接缓冲区和非直接缓冲区之分(关于两者的区别可以看 这里 ),它实际上也是一段内存空间。在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,它也是写入到缓冲区中的。流程如下图: 2. 2 通道Channel Channel(通道)表示到实体如硬件设备、文件、网络套接字或可以执行一个或多个不同I/O操作的程序组件的开放的连接。 Channel和传统IO中的Stream很相似。主要区别为: 通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作; 通道是一个对象,通过它可以读取和写入数据,当然了所有数据都通过Buffer对象来处理。我们永远不会将字节直接写入通道中,相反是将数据写入包含一个或者多个字节的缓冲区。同样不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。 2.3 选择器Selector Selector类是NIO的核心类,Selector(选择器)选择器提供了选择已经就绪的任务的能力。 Selector会不断的轮询注册在上面的所有channel,如果某个channel为读写等事件做好准备,那么就处于就绪状态,通过Selector可以不断轮询发现出就绪的channel,进行后续的IO操作。 一个Selector能够同时轮询多个channel。这样,一个单独的线程就可以管理多个channel,从而管理多个网络连接。这样就不用为每一个连接都创建一个线程,同时也避免了多线程之间上下文切换导致的开销。 与Selector有关的一个关键类是SelectionKey,一个SelectionKey表示一个到达的事件,这2个类构成了服务端处理业务的关键逻辑。关于SelectionKey的详细介绍可以参考这篇 博文 3. NIO编程 客户端代码: public class Client { ByteBuffer writeBuffer = ByteBuffer.allocate(1024); ByteBuffer readBuffer = ByteBuffer.allocate(1024); public void start() throws IOException{ //打开socket通道 SocketChannel sc = SocketChannel.open(); sc.configureBlocking(false); sc.connect(new InetSocketAddress("localhost",3400)); //创建选择器 Selector selector = Selector.open(); //将channel注册到selector中 sc.register(selector, SelectionKey.OP_CONNECT); Scanner scanner = new Scanner(System.in); while (true){ selector.select(); Set keys = selector.selectedKeys(); System.out.println("keys:"+keys.size()); Iterator iterator = keys.iterator(); while (iterator.hasNext()){ SelectionKey key = iterator.next(); iterator.remove(); //判断此通道上是否在进行连接操作 if (key.isConnectable()){ sc.finishConnect(); //注册写操作 sc.register(selector,SelectionKey.OP_WRITE); System.out.println("server connected..."); break; }else if (key.isWritable()){ System.out.println("please input message:"); String message = scanner.nextLine(); writeBuffer.clear(); writeBuffer.put(message.getBytes()); //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位 writeBuffer.flip(); sc.write(writeBuffer); //注册写操作,每个chanel只能注册一个操作,最后注册的一个生效 //如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来 //int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; //使用interest集合 sc.register(selector,SelectionKey.OP_WRITE | SelectionKey.OP_READ); }else if(key.isReadable()){ System.out.print("receive message:"); SocketChannel client = (SocketChannel) key.channel(); //将缓冲区清空以备下次读取 readBuffer.clear(); int num = client.read(readBuffer); System.out.println(new String(readBuffer.array(),0, num)); //注册写操作,下一次写 sc.register(selector, SelectionKey.OP_WRITE); } } } } public static void main(String[] args) throws Exception { new Client().start(); } } 服务端代码: /** * nio是面向缓冲区的 * bio是面向流的 * @author zmrwego * @descreption * @create 2018-10-15 **/ public class Server { private Selector selector; private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//调整缓冲区大小为1024字节 private ByteBuffer sendBuffer = ByteBuffer.allocate(1024); String str; public void start() throws IOException{ //打开服务器套接字通道 ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); //服务器配置为非阻塞 即异步IO ssc.bind(new InetSocketAddress(3400)); //绑定本地端口 //创建选择器 selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT);//ssc注册到selector准备连接 //无限判断当前线程状态,如果没有中断,就一直执行while内容。 while(! Thread.currentThread().isInterrupted()){ selector.select(); //select()方法返回的值表示有多少个 Channel 可操作 Set keys = selector.selectedKeys(); Iterator keyIterator = keys.iterator(); while (keyIterator.hasNext()){//处理客户端连接 SelectionKey key = keyIterator.next(); if (!key.isValid()){ continue; } if (key.isAcceptable()){ accept(key); } if(key.isReadable()){ read(key); } if (key.isWritable()){ write(key); } keyIterator.remove(); //移除当前的key } } } private void read(SelectionKey key) throws IOException{ SocketChannel socketChannel = (SocketChannel) key.channel(); this.readBuffer.clear();//清除缓冲区,准备接受新数据 int numRead; try{ numRead = socketChannel.read(this.readBuffer); }catch (IOException e){ key.cancel(); socketChannel.close(); return; } str = new String(readBuffer.array(),0,numRead); System.out.println(str); socketChannel.register(selector,SelectionKey.OP_WRITE); } private void write(SelectionKey key) throws IOException, ClosedChannelException{ SocketChannel channel = (SocketChannel) key.channel(); System.out.println("write:"+str); sendBuffer.clear(); sendBuffer.put(str.getBytes()); sendBuffer.flip();//反转,由写变为读 channel.write(sendBuffer); //注册读操作 下一次进行读 channel.register(selector,SelectionKey.OP_READ); } private void accept(SelectionKey key) throws IOException { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = ssc.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); System.out.println("a new client connected "+clientChannel.getRemoteAddress()); } public static void main(String[] args) throws Exception { System.out.println("sever start..."); new Server().start(); } } 二、详细了解Java NIO 前言 JDK 1.4 后, Java 提供了一个全新的 IO API ,即 Java New IO 本文 全面 & 详细解析 Java New IO ,希望你们会喜欢 目录 储备知识:Java IO 1. 定义 即 Java New IO 是1个全新的、 JDK 1.4 后提供的 IO API 2. 作用 提供了与标准 IO 不同的 IO 工作方式 可替代 标准 Java IO 的 IO API 3. 新特性 对比于 Java IO , NIO 具备的新特性如下: 4. 核心组件 Java NIO 的核心组件 包括: 通道( Channel ) 缓冲区( Buffer ) 选择器( Selectors ) 下面将详细介绍: 5. 具体使用 5.1 基于通道 & 缓冲数据 具体步骤如下: // 1. 获取数据源 和 目标传输地的输入输出流(此处以数据源 = 文件为例) FileInputStream fin = new FileInputStream(infile); FileOutputStream fout = new FileOutputStream(outfile); // 2. 获取数据源的输入输出通道 FileChannel fcin = fin.getChannel(); FileChannel fcout = fout.getChannel(); // 3. 创建 缓冲区 对象:Buffer(共有2种方法) // 方法1:使用allocate()静态方法 ByteBuffer buff = ByteBuffer.allocate(256); // 上述方法创建1个容量为256字节的ByteBuffer // 注:若发现创建的缓冲区容量太小,则重新创建一个大小合适的缓冲区 // 方法2:通过包装一个已有的数组来创建 // 注:通过包装的方法创建的缓冲区保留了被包装数组内保存的数据 ByteBuffer buff = ByteBuffer.wrap(byteArray); // 额外:若需将1个字符串存入ByteBuffer,则如下 String sendString="你好,服务器. "; ByteBuffer sendBuff = ByteBuffer.wrap(sendString.getBytes("UTF-16")); // 4. 从通道读取数据 & 写入到缓冲区 // 注:若 以读取到该通道数据的末尾,则返回-1 fcin.read(buff); // 5. 传出数据准备:将缓存区的写模式 转换->> 读模式 buff.flip(); // 6. 从 Buffer 中读取数据 & 传出数据到通道 fcout.write(buff); // 7. 重置缓冲区 // 目的:重用现在的缓冲区,即 不必为了每次读写都创建新的缓冲区,在再次读取之前要重置缓冲区 // 注:不会改变缓冲区的数据,只是重置缓冲区的主要索引值 buff.clear(); 5.2 基于选择器(Selecter) 具体步骤如下: // 1. 创建Selector对象 Selector sel = Selector.open(); // 2. 向Selector对象绑定通道 // a. 创建可选择通道,并配置为非阻塞模式 ServerSocketChannel server = ServerSocketChannel.open(); server.configureBlocking(false); // b. 绑定通道到指定端口 ServerSocket socket = server.socket(); InetSocketAddress address = new InetSocketAddress(port); socket.bind(address); // c. 向Selector中注册感兴趣的事件 server.register(sel, SelectionKey.OP_ACCEPT); return sel; // 3. 处理事件 try { while(true) { // 该调用会阻塞,直到至少有一个事件就绪、准备发生 selector.select(); // 一旦上述方法返回,线程就可以处理这些事件 Set keys = selector.selectedKeys(); Iterator iter = keys.iterator(); while (iter.hasNext()) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); process(key); } } } catch (IOException e) { e.printStackTrace(); } 6. 文件复制实例讲解 实例说明:实现文件复制功能 实现方式:通道 FileChannel 、 缓冲区 ByteBuffer import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class Test { public static void main(String[] args) throws IOException { // 设置输入源 & 输出地 = 文件 String infile = "C:\\copy.sql"; String outfile = "C:\\copy.txt"; // 1. 获取数据源 和 目标传输地的输入输出流(此处以数据源 = 文件为例) FileInputStream fin = new FileInputStream(infile); FileOutputStream fout = new FileOutputStream(outfile); // 2. 获取数据源的输入输出通道 FileChannel fcin = fin.getChannel(); FileChannel fcout = fout.getChannel(); // 3. 创建缓冲区对象 ByteBuffer buff = ByteBuffer.allocate(1024); while (true) { // 4. 从通道读取数据 & 写入到缓冲区 // 注:若 以读取到该通道数据的末尾,则返回-1 int r = fcin.read(buff); if (r == -1) { break; } // 5. 传出数据准备:调用flip()方法 buff.flip(); // 6. 从 Buffer 中读取数据 & 传出数据到通道 fcout.write(buff); // 7. 重置缓冲区 buff.clear(); } } } 7. 与Java IO的区别
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 1.远程调用 1.1、什么是远程调用 一个项目调用另一个项目模块(功能) 调用过程:模拟浏览器过程 1、打开浏览器窗口 2、确定访问路径(请求方式:get) 3、访问(执行),并获得结果 4、显示内容 5、关闭页面 1.2、调用方式 RPC 自定义数据格式的远程调用方式。更偏向于底层,通信速度快,效率高 ---常见框架:dubbo Http 采用Http协议远程调用方式,规定了数据传输的格式,缺点是消息封装臃肿,现在热门的Rest风格,就可以通过Http协议来实现。 ---常见框架:HTTPClient、RestTemplate 1.3、区别 区别 http rpc | 速度 | 较慢 | 快 难度 | 灵活性 简单 | 灵活,跨平台、跨语言 复杂 | 1、创建客户端,打开一个浏览器 2、创建GET请求实例,相当于输入一个请求地址 3、发送请求 4、判断状态码200 5、获得响应内容 6、释放资源 2.基于HTTP协议远程调用 2.1对比 HttpClient更偏向于底层,学习时原理。相当于模拟浏览器。但操作比较繁琐。 RestTemplate对整个请求进行简化 实际开发中RestTemplate整合HttpClient 2.2 RestTemplate 语法 //1 创建核心类 RestTemplate restTemplate = new RestTemplate(); //get请求 ResponseEntity<返回值类型> entity = restTemplate.getForEntity('请求路径', 返回值类型.class ); //post请求 ResponseEntity<返回值类型> entity = restTemplate.postForEntity('请求路径',请求参数对象,返回值类型.class); //put请求 restTemplate.put('请求路径', 请求参数); //delete请求 restTemplate.delete('请求路径') 2.3 配置类的使用 步骤一:编写配置类,将需要new的对象交于spring进行管理 @Configuration //配置类,spring boot会自动扫描 public class 类{ @Bean //spring将管理此方法创建的对象 public RestTemplate restTemplate(){ return new RestTemplate(); } } 步骤二:在任意位置,通过@Resource进行注入 public class AdminClient{ @Resource //因为配置类已经构建对象,此处自动注入 public RestTemplate restTemplate; //..使用即可 } 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> VO(View Object):视图层,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。 DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的时为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式条用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。 DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。 PO(Persistent Object):持久化对象,他跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。 模型: 下面以一个时序图建立简单模型来描述上述对象在三层架构应用中的位置 用户发出请求(可能是填写表单),表单的数据在展示层被匹配为VO。 展示层把VO转换为服务层对应方法所要求的DTO,传输给服务层。 服务层首先根据DTO的数据构造(或重建)一个DO,调用DO的业务方法完成具体业务。 服务层把DO转换为持久层对应的PO(可以使用ORM工具,也可以不用),调用持久层的持久化方法,把PO传递给它,完成持久化操作。 对于一个逆向操作,如读取数据,也是用类似的方式转换和传递,略。 VO与DTO的区别 1.大家可能会有个疑问(在笔者参与的项目中,很多程序员也有相同的疑惑):既然DTO是展示层与服务层之间传递数据的对象,为什么还需要一个VO呢?对!对于绝大部分的应用场景来说,DTO和VO的属性值基本是一致的,而且他们通常都是POJO,因此没必要多此一举,但不要忘记这是实现层面的思维,对于设计层面来说,概念上还是应该存在VO和DTO,因为两者有着本质的区别,DTO代表服务层需要接收的数据和返回的数据,而VO代表展示层需要显示的数据。 2.用一个例子来说明可能会比较容易理解:例如服务层有一个getUser的方法返回一个系统用户,其中有一个属性是gender(性别),对于服务层来说,它只从语义上定义:1-男性,2-女性,0-未指定,而对于展示层来说,它可能需要用“帅哥”代表男性,用“美女”代表女性,用“秘密”代表未指定。说到这里,可能你还会反驳,在服务层直接就返回“帅哥美女”不就行了吗?对于大部分应用来说,这不是问题,但设想一下,如果需求允许客户可以定制风格,而不同风格对于“性别”的表现方式不一样,又或者这个服务同时供多个客户端使用(不同门户),而不同的客户端对于表现层的要求有所不同,那么,问题就来了。再者,回到设计层面上分析,从职责单一原则来看,服务层只负责业务,与具体的表现形式无关,因此,它返回的DTO,不应该出现与表现形式的耦合。 3.理论归理论,这到底还是分析设计层面的思维,是否在实现层面必须这样做呢?一刀切的做法往往会得不偿失,下面我马上会分析应用中如何做出正确的选择。 VO与DTO的应用 上面只是用了一个简单的例子来说明VO与DTO在概念上的区别,本节将会告诉你如何在应用中做出正确的选择。 在以下才场景中,我们可以考虑把VO与DTO二合为一(注意:是实现层面): 当需求非常清晰稳定,而且客户端很明确只有一个的时候,没有必要把VO和DTO区分开来,这时候VO可以退隐,用一个DTO即可,为什么是VO退隐而不是DTO?回到设计层面,服务层的职责依然不因该与展示层耦合,所以,对于前面的例子,你很容易理解,DTO对于“性别”来说,依然不能用“帅哥美女”,这个转换应该依赖与页面的脚本(如javaScript)或其他机制(JSTL,EL,CSS)。 即使客户端可以进行定制,或者存在多个不同的客户端,如果客户端能够用某种技术(脚本或其他机制)实现转换,同样可以让VO隐退。 以下场景需要优先考虑VO,DTO并存: 上述场景的反面场景 因为某种技术原因,比如某个框架(如Flex)提供自动把POJO转换为UI中某些FieId时,可以考虑在实现层面定义出VO,这个这个权衡完全取决于使用框架的自动转换能力带来的开发和维护效率提升与设计多一个VO所多做的事情带来的开发和维护效率的下降之间的对比。 如果页面出现一个“大视图”,而组成这个大视图的所有数据需要调用多个服务,返回多个DTO来组装(当然,这同样可以通过服务层提供一次性返回一个大视图的DTO来取代,但在服务层提供一个这样的方法是否合适,需要在设计层面进行权衡)。 DTO与DO的区别 首先是概念上的区别,DTO是展示层和服务层之间的数据传输对象(可以认为是两者之间的协议),而DO是对现实世界各种业务角色的抽象,这就引出了两者在数据上的区别,例如UserInfo和User(对于DTO和DO的命名规则,请参见笔者前面的一篇博文),对于一个getUser方法来说,本质上它永远不应该返回用户的密码,因此UserInfo至少比User少一个password的数据。而在领域驱动设计中,正如第一篇系列文章所说,DO不是简单的POJO,它具有领域业务逻辑。 DTO与DO的应用 1.从上一节的例子中,细心的读者可能会发现问题:既然getUser方法返回的UserInfo不应该包含password,那么就不应该存在password这个属性定义,但如果同时有一个createUser的方法,传入的UserInfo需要包含用户的password,怎么办?在设计层面,展示层向服务层传递的DTO与服务层返回给展示层的DTO在概念上是不同的,但在实现层面,我们通常很少会这样做(定义两个UserInfo,甚至更多),因为这样做并不见得很明智,我们完全可以设计一个完全兼容的DTO,在服务层接收数据的时候,不该由展示层设置的属性(如订单的总价应该由其单价、数量、折扣等决定),无论展示层是否设置,服务层都一概忽略,而在服务层返回数据时,不该返回的数据(如用户密码),就不设置对应的属性。 2.对于DO来说,还有一点需要说明:为什么不在服务层中直接返回DO呢?这样可以省去DTO的编码和转换工作,原因如下: 两者在本质上的区别可能导致彼此并不一一对应,一个DTO可能对应多个DO,反之亦然,甚至两者存在多对多的关系。 DO具有一些不应该让展示层知道的数据。 DO具有业务方法,如果直接把DO传递给展示层,展示层的代码就可以绕过服务层直接调用它不应该访问的操作,对于基于AOP拦截服务层来进行访问控制的机制来说,这问题尤为突出,而在展示层调用DO的业务方法也会因为事务的问题,让事务难以控制。 对于某些ORM框架(如Hibernate)来说,通常会使用“延迟加载”技术,如果直接把DO暴露给展示层,对于大部分情况,展示层不在事务范围之内(Open session in view在大部分情况下不是一种值得推崇的设计),如果其尝试在Session关闭的情况下获取一个未加载的关联对象,会出现运行时异常(对于Hibernate来说,就是LazyInitiliaztionException)。 从设计层面来说,展示层依赖于服务层,服务层依赖于领域层,如果把DO暴露出去,就会导致展示层直接依赖于领域层,这虽然依然是单向依赖,但这种跨层依赖会导致不必要的耦合。 对于DTO来说,也有一点必须进行说明,就是DTO应该是一个“扁平的二维对象”,举个例子来说明:如果User会关联若干个其他实体(例如Address、Account、Region等),那么getUser()返回的UserInfo,是否就需要把其关联的对象的DTO都一并返回呢?如果这样的话,必然导致数据传输量的大增,对于分布式应用来说,由于涉及数据在网络上的传输、序列化和反序列化,这种设计更不可接受。如果getUser除了要返回User的基本信息外,还需要返回一个AccountId、AccountName、RegionId、RegionName,那么,请把这些属性定义到UserInfo中,把一个“立体”的对象树“压扁”成一个“扁平的二维对象”,笔者目前参与的项目是一个分布式系统,该系统不管三七二十一,把一个对象的所有关联对象都转换为相同结构的DTO对象树并返回,导致性能非常的慢。 DO与PO的区别 DO和PO在绝大部分情况下是一一对应的,PO是只含有get/set方法的POJO,但某些场景还是能反映出两者在概念上存在本质的区别: DO在某些场景下不需要进行显式的持久化,例如利用策略模式设计的商品折扣策略,会衍生出折扣策略的接口和不同折扣策略实现类,这些折扣策略实现类可以算是DO,但它们只驻留在静态内存,不需要持久化到持久层,因此,这类DO是不存在对应的PO的。 同样的道理,某些场景下,PO也没有对应的DO,例如老师Teacher和学生Student存在多对多的关系,在关系数据库中,这种关系需要表现为一个中间表,也就对应有一个TeacherAndStudentPO的PO,但这个PO在业务领域没有任何现实的意义,它完全不能与任何DO对应上。这里要特别声明,并不是所有多对多关系都没有业务含义,这跟具体业务场景有关,例如:两个PO之间的关系会影响具体业务,并且这种关系存在多种类型,那么这种多对多关系也应该表现为一个DO,又如:“角色”与“资源”之间存在多对多关系,而这种关系很明显会表现为一个DO——“权限”。 某些情况下,为了某种持久化策略或者性能的考虑,一个PO可能对应多个DO,反之亦然。例如客户Customer有其联系信息Contacts,这里是两个一对一关系的DO,但可能出于性能的考虑(极端情况,权作举例),为了减少数据库的连接查询操作,把Customer和Contacts两个DO数据合并到一张数据表中。反过来,如果一本图书Book,有一个属性是封面cover,但该属性是一副图片的二进制数据,而某些查询操作不希望把cover一并加载,从而减轻磁盘IO开销,同时假设ORM框架不支持属性级别的延迟加载,那么就需要考虑把cover独立到一张数据表中去,这样就形成一个DO对应对个PO的情况。 PO的某些属性值对于DO没有任何意义,这些属性值可能是为了解决某些持久化策略而存在的数据,例如为了实现“乐观锁”,PO存在一个version的属性,这个version对于DO来说是没有任何业务意义的,它不应该在DO中存在。同理,DO中也可能存在不需要持久化的属性。 DO与PO的应用 由于ORM框架的功能非常强大而大行其道,而且JavaEE也推出了JPA规范,现在的业务应用开发,基本上不需要区分DO与PO,PO完全可以通过JPA,Hibernate Annotations/hbm隐藏在DO之中。虽然如此,但有些问题我们还必须注意: 对于DO中不需要持久化的属性,需要通过ORM显式的声明,如:在JPA中,可以利用@Transient声明。 对于PO中为了某种持久化策略而存在的属性,例如version,由于DO、PO合并了,必须在DO中声明,但由于这个属性对DO是没有任何业务意义的,需要让该属性对外隐藏起来,最常见的做法是把该属性的get/set方法私有化,甚至不提供get/set方法,但对于Hibernate来说,这需要特别注意,由于Hibernate从数据库读取数据转换为DO时,是利用反射机制先调用DO的空参数构造函数构造DO实例,然后再利用JavaBean的规范反射出set方法来为每个属性设值,如果不显式声明set方法,或把set方法设置为private,都会导致Hibernate无法初始化DO,从而出现运行时异常,可行的做法是把属性的set方法设置为protected。 对于一个DO对应多个PO,或者一个PO对应多个DO的场景,以及属性级别的延迟加载,Hibernate都提供了很好的支持,请参考Hibnate的相关资料。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> https://blog.csdn.net/youanyyou/article/details/82142014 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> https://blog.csdn.net/litianxiang_kaola/article/details/86646947 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 阿姆达尔定律 = Amdahl's Law,本篇重点是介绍该定律。 背景 系统架构中对系统性能设计的解决方案之一:“阿姆达尔方案”。书中还有个完全看不懂的公式,理解起来脑仁痛(不懂...)但发现很有价值,便系统的学习一下。 例题试先做一下,带着疑问点再看定律。 例题 :假设某一功能的处理时间为整体系统运行时间的60%,若使该功能的处理速度提高至原来的5倍,则根据阿姆达尔定律,整个系统的处理速度可提高至原来的多少倍? 定理公式 优化前系统总耗时To(old),优化后系统总耗时Tn(new),加速比S=To/Tn。 Speedup = timeOld / timeNew 详细公式: S = 1 / ((1-C) + C/S) S(speedup),加速比。 C,原系统中能够改进的部分占总部分的比例,也可以说能够改进部分运行时间占总系统运行时间的比例,比如:75%。 S,改进后系统的提升比例,比如:5倍。 举例分解 上面的例子细化一下:程序A中子程序B的运行时间占A的60%,子程序B优化提升了5倍速度,那么程序A将多少? 程序A初始假设速度可为1,如下: 1/((1-0.6)+0.6/5) = 1/(0.4+0.12) = 1/0.52 = 1.923 Amdahl 阿姆达尔 Gene Amdahl,出生于1922年11月16日,出生在美国南达科他州的弗德鲁,是著名的企业家,创办多家公司。Amdahl曾经是研究并行处理系统的,1967年Amdahl推导出了固定负载情况下描述并行处理效果的加速公式。 P是并行率。若P=1,获取到无穷加速比; 定律的理解 本篇要说的是它的一种变形运用。 定义:系统中对某一部件采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。 阿姆达尔定律 实际上 定义了采取增强(加速)某部分功能处理的措施后可获得的性能改进或执行时间的加速比。简单来说: 要想显著加速整个系统,必须提升全系统大部分的速度 。可以再简单些: 优化影响最大的部分 。 建议采用这样的思路来优化系统性能,也可以直接采用公式来量化改进效果。这个公式在操作上十分的简单很实用,可以运用在整个系统分析上,也可以在模块分析上,甚至是一个方法上。 友链支持 Gene Amdahl 百度定义 Amdahl's Law 参考博客: codingforspeed.com 、 ifeve.com 作者:Owen Jia <专注技术架构> 作者博客: https://blog.shareworld.vip 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 目前 Knative 中默认支持是基于域名的转发,但域名默认格式是:"{{.Name}}.{{.Namespace}}.{{.Domain}}"(这个可以在 config-network 配置)。但对于用户来说并不能指定全域名。 另外一个问题就是基于Path 转发的能力,很多情况下我们是基于一个域名,不同服务通过访问路径进行区分,但当前的Knative 默认是不支持设置 Path 转发能力。 针对这两个问题,我们在阿里云 Knative 中提供了这样的能力,用户可以通过控制台配置域名,并基于Path和Header进行转发规则设置。 配置域名 登录 阿里云容器服务控制台 ,进入【Knative】-【组件管理】,点击 Serving 组件【详情】: 进入详情之后,选择 域名配置 ,添加自定义域名: 这里添加域名需要注意,Knative服务默认创建的域名是不允许添加的。哪些是默认创建的域名呢? 其实在Knative 中每创建一个服务都会创建对应的 VirtualService, 如 helloworld-go 对应的 VirtualService 中默认域名如下: hosts: - helloworld-go.default - helloworld-go.default.svc - helloworld-go.default.svc.cluster.local 配置路由规则 登录 阿里云容器服务控制台 ,进入【Knative】-【服务管理】,选择对应的服务。这里我们选择 helloworld-go 服务: 进入服务详情之后,选择 路由转发 页签, 创建路由规则: 接下来我们分别说明基于路径和 Header 的转发。 基于路径转发 在路由规则中设置不通的路径实现基于路径的服务转发, 这里我们在 helloworld-go 服务中,选择 hello.serverless.kuberun.com 域名并设置 /test 路径: 访问页面: http://hello.serverless.kuberun.com/test 结果如下: 基于路径与Header转发 在 helloworld-go 服务中,选择 hello.serverless.kuberun.com 域名并设置 /test 路径, 并设置Header foo:bar : 这时候如果我们再次直接访问: http://hello.serverless.kuberun.com/test ,会发现无法访问。这时候我们需要设置Header(可以通过ModHeader 插件设置请求Header)。 》》阿里云双11亿元补贴提前领,进入抽取iPhone 11 Pro 阅读原文 本文为云栖社区原创内容,未经允许不得转载。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> git命令总结 Study: https://www.bilibili.com/video/av55780016 often 仓库克隆 git clone git@github.com:BenCoper/BeginJava.git(仓库地址) 查看状态 git status 设置用户 git config --global user.email ysjcode@gmail.com 设置用户名 git config --global user.name BenCoper 查看日志 git log 添加暂存 git add 文件名 推送仓库 git commit -m "添加提交信息" 密钥 创建密钥 ssh-keygen -t rsa -b 4096 -C "ysjcode@gmail.com"(邮箱地址) 密钥复制 clip < ~/.ssh/id_rsa.pub 冲突解决 查看commit事件 git show ....(commitID,日志中查看) 回滚暂存 git reset (后面可接commitID,回滚单个) 推送远端 git push 更新 git pull 团队协作 创建分支 git branch 分支名称 切换分支 git checkout 分支名称 设置远端分支 git push --set-upstrean orgin 分支名称 切换第二分支 git checkout -b 分支名称2 合并分支到master git merge 分支名称(1或2) 其他 忽略.idea文件 git文件内添加 .idea/ 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 联系方式; ysjcode@gmail.com 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 出现Unsupported major.minor version 52.0,其实就是编译版本和Tomcat启动版本不符所导致的。 使用jdk1.8编译后,在tomcat6上运行会出现此编译错误。 将配置更改一致即可解决此问题。 参考: https://www.cnblogs.com/jpfss/p/9036645.html 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 今天遇见了一个奇怪的问题,在IDE中run unit test,全部cases都成功了,但是后来通过mvn test运行case确保错了。在寻求原因的同时也找到了对应的解决方法。 Run Unit Test和Maven test的区别 差异1:在IDE中通过选中单元测试路径,点击右键选择run test和点击maven中的test是有区别的。在Maven执行测试的过程中,是不允许测试cases访问其他项目的测试类和其他项目的resources下文件的。也就是说,在a/src/test/java下的测试用例,是不能引用b/src/test/java中的类的,同时也不允许访问b/src/test/resources下的资源的。但是在IDE中的Run Unit Test几乎是没有这样的限制的。 差异2:Maven强制要求src/test/java下不能存在resource的文件,必须放到src/test/reources文件夹下,但是IDE却很少有对应的约束。 这些约束就是导致IDE下Run Unit Test是成功的,但是在Maven中失败的原因。 因此测者提醒,提交单元测试代码之前,一定要在本地mvn test一次脚本。 解决的办法 在maven插件配置:(surefire2.14以下版本) org.apache.maven.plugins maven-surefire-plugin 2.12 always 重点加入configureation的配置部分 在maven插件配置:(surefire2.14及其以上版本) org.apache.maven.plugins maven-surefire-plugin 2.19.1 false 1 在2.14以上的版本中,forkMode配置项已经废弃了。 另一个可能有效的方法 有时候在webapp项目中进行测试的时候,需要WEB-INF文件夹放在Class Path中,配置如下: org.apache.maven.plugins maven-surefire-plugin 2.19.1 false 1 -Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8 -Xverify:none ${basedir}/src/main/webapp/WEB-INF/ 关注测者,关注测试 关注我,关注测试 FROM: https://blog.csdn.net/crisschan 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 摘要 在 详解http报文 相关文章中我们介绍了http协议是如何工作的,那么构建一个真实的网站还需要引入组件呢?一些常见的名词到底是什么含义呢? 什么叫正向代理,什么叫反向代理 服务代理与负载均衡的差别 有了nginx,为啥还需要LVS 都有哪些负载均衡的方式 服务端演进 在前面文章中我们介绍过最简单的一种客户端-服务端响应模式,如下 这是http服务最简单的一种形式,服务端就一层web服务器。 现在我们服务端变复杂了,用户数增加了,并发量增加了。对我们服务端要求增加了 服务能力:一台服务器满足不了这么多的http的请求了。我们需要增加机器了,进行服务扩容了 安全防护:开始有人对我们的服务进行网络攻击了,需要保护服务端服务器,限制ip地址 网站升级: 网站上线后,需要提供7*24小时无间断服务了,发布新的版本,需要保证网站的可用。 代理服务 为了解决这些问题,我们需要引入 中间层 也就是代理,在客户端和服务端中间插入一个中间环节,代理服务。代理,狭义上讲就是不生产内容,只是转发上下游的请求和响应。 代理服务按照是否匿名可以分为 匿名代理: 外部不知道真实机器,只知道代理服务器 透明代理: 外界知道代理,也知道真实服务器 按照靠近客户端还是服务端,分为 正向代理: 代理客户端,代表着客户端向服务器端发送请求 反向代理: 代理服务端,代表着服务器向客户端发送请求。 http协议对代理的支持 因为http协议最开始并没有考虑代理服务,设计的协议只是针对客户端-服务器模式。根据我们通常的架构标准,http协议层是不用关心使用者是如何使用的,代理服务这种中间产物自然不用考虑。服务端有获取客户端ip的需求,所以Squid这个缓存代理软件最先引入 X-Forwarded-For 头字段,用来表示 客户端的真实 IP。 格式如下,从客户端到各个代理服务,记录下每一层的转发 X-Forwarded-For: client, proxy1, proxy2 这个需求是如此的普世,所以慢慢变成了标准,被各个代理服务广泛使用,所以后来被写入到 RFC 7239 标准之中了 代理协议 HTTP 协议本身对代理服务并没有什么说明,所以就衍生出了代理协议,代理协议是haproxy的作者Willy Tarreau于2010年开发和设计的一个Internet协议,通过为tcp添加一个很小的头信息,来方便的传递客户端信息(协议栈、源IP、目的IP、源端口、目的端口等),在网络情况复杂又需要获取客户IP时非常有用。 多层NAT网络 TCP代理(四层)或多层tcp代理 https反向代理http(某些情况下由于Keep-alive导致不是每次请求都传递x-forword-for) https通信加密,不允许修改原始报文 另外由于每一层代理服务都需要解析http header 头 X-Forwarded-For ,然后追加自己的地址,所以这个成本也比较高。所以代理协议也变成了 刚需 ,虽然是haproxy提出来的,但是也被各大代理服务器支持了,如nginx、apache、squid。代理协议格式 PROXY TCP4/TCP6 客户端ip 应答方ip 请求方端口号 应答方端口号 \r\n 这样请求方解析第一行就可以拿到客户端ip,不用再去处理http报文了。 负载均衡 负载均衡,其实就是分发请求。根据OSI七层协议 负载均衡分成两种 4层负载均衡,即工作在第四层传输层,利用ip地址端口进行请求转发,因为没有其他操作,所以效率比较高 七层负载均衡,即工作在第七层应用层,根据HTTP请求头,URL信息转发特定的主机。效率相对低一点。 nginx是七层负载均衡,LVS是七层负载均衡。 所以小型网站,nginx就足够,当流量足够大时,负载均衡成为瓶颈了,就可以在前面引入了LVS一层。 关于具体的负载均衡算法, 参考这边文章 ,这里不再赘述 安全防护 前面我们提到过安全防护也是代理服务的一大重要功能。为了应对外部攻击,需要引入网络防火墙,WAF(Web Application Firewall)。工作在OSI 第七层,主要是对http报文进行更细致的审核,也就是各种filter。 比如 IP 黑白名单 DDOS攻击 各种注入 当服务的安全性要求没那么高时,或者对公司业务发展的ROI没那么高时,我们通常就在nginx层面配置一些规则即可。需求升级时,我们就要引入专门的模型,比如 ModSecurity1 。需求再升级时,引入外部云厂商提供的WAF服务。 最终架构形式 http服务端架构演进和我们单应用架构演进有异曲同工之处。在业务不复杂的时候,可以使用单体模块搞定(比如Nginx),当请求量增加,需求升级时,需要引入中间层来解决。当某个模块要求增加时,需要解耦出单独的模块来处理。 所以整体上看,一个中型的服务端架构如下图。 参考 https://juejin.im/post/5ccaaf0af265da035e213490 https://www.cnblogs.com/xybaby/p/7867735.html 关注公众号【方丈的寺院】,第一时间收到文章的更新,与方丈一起开始技术修行之路 相关阅读 详解http报文 详解http报文(2)-web容器是如何解析http报文的 > 本文由博客一文多发平台 OpenWrite 发布! 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 现在二维码种类比较多,为了突出二维码的个性及吸引客户,很多朋友都喜欢在二维码上插入图片。想要每个二维码都与众不同,但是有的时候需要批量插入图片数量有限,如果制作的二维码比较多的话,就没图片可以插入了。这时我们可以利用条码打印软件中的脚本编程功能在二维码中循环批量插入以下29张图片,具体操作如下: 1.在条码打印软件新建标签绘制二维码对象之后,点击软件左侧的”插入位图图片按钮”,在二维码上绘制一个二维图对象,双击位图,在图形属性-数据源中,点击”+”号按钮,弹出”数据管理窗口”,点击浏览,弹出”打开”窗口,在打开窗口中,选择要插入的图片,点击打开,在点击添加。 在数据源中我们可以看到,图片路径已经被添加上了,点击”修改”按钮,在数据对象管理对话框中,我们可以把序列号和图片的后缀名去掉,点击编辑。 2.文件夹所有图片名称是有序列号和后缀名组成的,点击”+”号按钮,数据对象类型选择”序列生成”,开始字符串为0,点击添加 3.添上固定的后缀名,再点击”+”号按钮,数据对象类型选择”手动输入”,在下面的状态框中,输入.jpg,点击添加 选中左侧的序列号,在右侧的处理方法中,点击”+”号按钮,处理方法类型选择”脚本编程”,在脚本编程中把默认的strReturn = "123";修改成strReturn = strReturn%29+1;, 在点击”+”号按钮,处理方法类型选择”丢弃”,位置为右端,长度为2,点击添加。 设置好之后,可以在二维码下方绘制一个普通文本,双击普通文本,在图形属性-数据源中,点击”修改”按钮,数据对象类型选择”数据引用” ,引用图片的ID,具体操作可以参考:条码打印软件引用其他对象内容的方法。我们可以更直观的看到图片路径是否跟上图一致。 之后可以点击软件上方工具栏中的”打印预览”,看下预览效果。 友情提示:在二维码上循环批量插入图片使用脚本编程的时候,需要在数据对象中选择序列生成,然后在处理方法中,处理方法类型中选择脚本编程,不是选择.JPG,然后再设置脚本编程。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 概述 目前阿里云官方对于微消息队列 MQTT提供了很多语言的参考示例,但是在实际的使用中发现很多用户在使用Android Sample的时候总是会遇到问题,无法正常调试使用。本文主要介绍Android Sample的使用。 具体操作步骤 1、accessKey,secretKey获取参考链接: 阿里云常见参数获取位置 2、serverUri、instanceId参数获取: 3、clientId参数的格式为:GroupId@@@DeviceName,GroupId需要在控制台预创建,DeviceName可以任意设置,确保不要重复即可。 4、Topic获取 具体参数的含义可以参考链接: 微消息队列 MQTT名词解释 5、Demo下载地址: lmq-andriod-demo 6、Android Config文件配置格式参考( 注意: serverUri前面需要加上 tcp://) package mqtt.demo; public class Config { public static final String serverUri = "tcp://post-cn-0pp13ag1111.mqtt.aliyuncs.com"; public static final String clientId = "GID_MQTTDemo@@@********"; public static final String instanceId = "post-cn-0pp13ag1111"; public static final String accessKey = "********"; public static final String secretKey = "********"; public static final String topic = "********"; } 7、Android测试运行日志 8、连接查询 参考链接 MQTT 快速入门 如何使用MQTT.fx连接微服务消息队列 阅读原文 本文为云栖社区原创内容,未经允许不得转载。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 1 背景概述 在软件行业飞速发展的今天,我们可以将软件公司分大体分为两类,一类是使用框架进行开发的软件公司,另一类是套装软件产品的提供商,前者公司多数定位是项目类公司,后者则可以称为产品类公司。但做产品与做项目有哪些区别,大多数的人面对这个问题还是较为模糊的,甚至简单认为两者是没有区别的,均是程序开发而已。但事实并非如此,做产品与做项目两者之间既存在本质的区别,也存在着紧密的联系,今天笔者在这里将自己理解与大家分享。 2 定义及周期 2.1 项目定义 项目: 是指在一定的约束条件下(主要是限定时间、限定资源),具有明确目标的工作任务。 软件项目: 是指为企业开发或者部署实施一套专用的系统,或在特定的行业领域做一些系统之间的集成,在进入项目之前必须与用户进行具体的交流和讨论,了解清楚用户心目中的产品或项目预期是什么样子,然后招投标、签订合同、实施交付。 2.2 项目周期 软件项目的生命周期是软件的产生直到报废的过程,包括项目的启动、需求调研、功能设计、业务开发、项目测试、项目验收交付给用户,项目结项后项目生存周期结束。随着时间的推移以及发展,为满足当前发展的需求项目通常会重新定义开发。软件项目的生命周期图如下: 2.3 产品定义 产品: 是指能够提供给市场,被人们使用和消费,并能满足人们某种需求的任何东西,包括有形的物品、无形的服务、组织、观念或它们的组合。 软件产品: 是指向用户提供的计算机软件、信息系统、套装软件或在提供计算机信息系统集成、应用服务等技术服务时提供的软件,是通用的产品应用于某一行业领域而不是像软件项目一样为某一需求或者单位定制开发。 2.4 产品周期 软件产品的生存周期类似于人的成长,从出生(产品构思),到成长(产品的版本更新),到去世(产品中止)的过程。产品不存在完成的说法,因为产品是不断更新的,直到被新产品替代,生存周期才结束。软件产品的生命周期图如下: 3 区别与联系 3.1 两者区别 做产品与做项目的区别,大多数人可能都认为两者是相同的,均是使用不同的编程语言进行项目/产品的开发,但其实做产品与做项目两者是存在本质性区别的,下面我们通过两者的出发点,品质要求,时间成本,任务分工,团队构成以及最终的结果导向来分别阐述两者的区别。 3.1.1 驱动因素 项目的驱动因素: 做项目侧重于时间驱动,因为时间就是成本,要压缩成本就要压缩时间,在功能上力求操作敏捷、易用、友好,如果在项目时间紧迫的情况下,至少要能保证每个功能都好用、不出现BUG。 做项目是以客户的需求为驱动,按照客户的需求进行定制开发,对于不明确的需求要第一时间与客户进行沟通,不可存在需求不明确的现象。 产品的驱动因素: 做产品侧重于功能驱动,做产品的时间相对来说比较充足,所以要以开发出有竞争力、受广大客户欢迎的产品为原则,功能响应速度要快,操作要简便、界面要美观。 做产品是为了满足某一应用市场而针对性进行一套装软件或一个产品的开发,对于产品的性能以及快速迭代扩展的要求更高,产品的需求也并不像软件项目一样完全明确,存在着后期根据需求、迭代升级的情况。 3.1.2 质量要求 项目的质量要求: 做项目的第一准则是客户的需求,项目的开发人员需要依据客户的需求进行定制开发,并且项目需要保证功能适用于当前客户的使用习惯,性能稳定。 项目的质量更加侧重于某一客户的具体需求,保证交付的软件项目程序可运行、维护,需求功能可实现。 产品的质量要求: 产品的质量要求更加侧重于某一行业领域的应用场景,某一款产品需要适应或囊括当前行业可能涉及的需求,所能匹配的应用性更为广泛,并且对产品逻辑、代码运维性的要求更高。 做产品的性能必须尽量优化,因为产品为提升竞争力就必须比同类产品更好用,更敏捷,而且产品是一个不断完善升级的过程,对代码的框架以及维护性都具有更高的要求。 3.1.3 时间投入 项目的时间投入: 做项目的时间投入一般是根据项目的需求,进行评估。通常是从项目启动、需求调研、功能设计、业务开发、测试运行、验收交付为一个周期。 项目有明确时间约束,什么时候开始,什么时候结束,每个节点都需要一目了然。通常以项目的验收单作为分项的里程碑及整体验收单作为项目的交付证明。 产品的时间投入: 做产品的时间相对来说比较长,产品通常更加关注的是整个产品的规划、开发、推广、维护等。 产品时间一般来说可以明确开始时间却不能明确真正的结束时间,因为产品是一直在进行迭代完善的过程,通常会通过不同的产品版本来区分维护、优化、升级。 3.1.4 工作区分 项目的工作区分: 做项目通常将工作按照业务功能模块划分,明确每个模块的负责人,各负责人梳理负责功能的需求,把握当前工作进度。 做项目的核心人物是项目经理,项目经理需要与客户经常沟通交互,之后由项目经理统一进行任务分工与安排,不定时的进行代码review、项目内部沟通会议以及项目进度的推进把控。 产品的工作区分: 做产品更加关注的是整个产品的生命周期,明确每个功能的负责人,将重点放在产品的功能完善中。 做产品的核心人物是产品经理,产品经理负责把控产品的整体研发方向,带领研发团队进行产品研发,并且在产品研发过程中注重考虑产品的扩展性、兼容性、安全性、推广性。 3.1.5 团队构成 项目的团队构成: 做项目团队一般由项目经理、架构设计师、需求分析员、软件工程师、测试工程师构成。 产品的团队构成: 做产品团队一般由产品经理、交互设计师、项目管理人员、开发人员、运维人员构成。 3.1.6 结果导向 项目的结果导向: 项目的结果是以项目的验收单为基准,项目主要是满足客户特定的需求,体现个性化。 项目多数是针对某一企业或客户的,功能相对特殊化、单一化、比较有针对性,项目在产品开发周期中可做为产品的雏形。 产品的结果导向: 做产品最终的结果是给用户使用的,成功的产品将会被大面积普及应用。 产品是面向大众的,更侧重市场的需要,有广泛性。产品相对而言有比较固定的价格。 3.2 内在联系 做产品与做项目有区别的同时也存在着联系,很多时候两者之间是没有明显的界限的,项目中包含产品、产品应用于项目。下面我们通过两者间的共同目标、使用相同的开发语言、具有重合的应用场景、实施与维护、均可以为公司获取经济利益等方面来分别阐述。 3.2.1 相互融合 当前的软件项目中,做项目可以作为做产品的原型,做产品也可以应用于项目之中。两者之间是没有明显界限的,项目中包含产品,产品应用于项目。 当前的项目存在于传统企业中,而当前互联网厂商以及金融企业(如:银行、证券等)内部将应用系统统称为产品,该项目为自己内部开发,或者只有少部分的资源是外部进行开发。 在软件开发商中如:电商企业对于企业开发的网上商城等应用系统,将项目作为产品来开发,统称为电商平台产品。 3.2.2 共同目标 做项目与做产品两者的目标是相同的,均是为了更好的为客户服务,满足客户的需求,帮助客户进行信息化平台的建设。 无论是做项目还是做产品两者的终极目的均是为了公司获取经济利益,支撑公司的运营与发展、让公司股东、员工都从其中受益。 3.2.3 开发语言 无论是做项目还是做产品均是由开发人员根据不同的需求进行开发/研发的,同一系列的项目与产品是使用相同的开发语言进行开发的。 在做项目与做产品的过程中均是由需求调研、产品/功能设计、程序开发、调整、完善、软件测试流程组成。 无论是做项目还是做产品均是由项目/产品的负责人进行工作划分,都要明确负责人,细分至事、人、天,保证最终目标顺利完成。 3.2.4 应用场景 在项目中包含产品同时,产品应用于项目之中,但做项目中不一定会百分百有产品,或者说不是所有项目都能(会)孵化产品。 无论是做项目还是做产品均要应用于XXX客户平台中心,将项目/产品部署于客户提供的服务器中,为客户更好的应用。 3.2.5 实施维护 无论是做项目还是做产品在交付客户之后均需要开发/研发人员进入客户现场对客户方的技术人员/使用人员进行培训。 无论是做项目还是做产品都需要交付客户相应的维护文档以及使用手册,对当前的交接人员进行实施交付。 无论是做项目还是做产品都需要开发/研发人员进行实施维护,对项目/产品进行为期一年(可根据实施情况)的维护。 3.2.6 经济收益 无论是做项目与做产品都可以为对应的参与人员都会获得相应的收益,产品项目/产品的开发人员不仅可以获取相应的奖金,也可以获取对应的个人能力的提升。 无论是好的项目还是好的产品交付客户以后都可以为公司带来品牌效益,可以通过客户之间的相互介绍为公司带来更多的项目/产品的收益。 4 能力的要求 沈阳数通畅联定位是SOA集成产品和技术解决方案提供商,但是现阶段很多集成项目都是由数通畅联自己来交付。项目经理是项目交付的核心人物,产品经理是产品开发的核心人员,有些公司的产品产品经理不开发,主要工作是通过产品市场来分析进行功能设计、项目经理不写代码只做沟通管理、项目推进等工作。但在数通畅联项目经理和产品经理都有代码开发工作,比如:项目经理负责整个项目设计、评审以及通用功能的开发,产品经理则负责产品整体升级、核心功能开发。 在沈阳数通畅联:项目经理跟产品经理角色在公司里都非常重要,但二者能力模型是略有不同,通常来说产品经理比项目经理高一个级别,产品经理必须要经历项目经理,比如说:初级产品经理跟中级项目经理级别相当,但不是所有的项目经理都一定能成为产品经理,也不是产品经理一定比项目经理更厉害或者说更重要,不能一概而论。下面我们将分别对项目经理、产品经理的工作侧重点进行对比介绍: 4.1 项目经理 4.1.1 沟通交流 作为项目经理80%的时间在沟通,剩余20%的时间在准备沟通,不仅是与客户沟通、领导沟通,与项目组成员也需要时常沟通保证项目的进度。 在与客户沟通的时候,更多是倾听对方的需求,不是马上给出答复,分析之后再给出答复。 在与领导或者同事沟通时,有问题自己思考之后,准确描述问题,快速暴漏问题,这样才能辅助你解决问题。 4.1.2 项目推进 项目经理需要每周撰写周总结,说明当周的工作内容,下周计划内容以及后续计划安排。每天早上询问当天的工作内容将可能遇见的技术点进行讲解,如果没有整体时间进行工作事项确认时,可以分项进行,将工作分解分项进行。 在项目的前期需要明确开发规范,强调开发规范,命名规则。前期每天、后期每周三、五进行代码review。 根据每位员工的个人能力的不同,分别进行任务分配,将功能分工明确至事、人、天。这样可以进一步的保证当前任务是可考核的进而保证项目的进度是可控的。 4.1.3 促进验收 项目的最终目的就是为了验收,项目经理需要时刻保持验收的心态,积极促进项目的验收,不是将当前工作全都做完才能提出验收,而是项目进入验收阶段与客户沟通项目相关的验收事宜。 项目经理需要引导客户结款的意识,不要客户说什么就是什么,要明确与钱挂钩(常用话术:这个在需求阶段并没有提出,而且整体的流程是需要梳理明确的,这个会涉及到商务的事情,需要和商务具体来谈)。 4.2 产品经理 4.2.1 整体把握 作为产品经理需要能够站对市场前景、应用场景、方案组合、盈利模式都有清晰的认知,能够从用户的角度去思考,深度挖掘用户的深层次的需求,善于抽象、归纳把用户需求转换为功能需求。 明确产品最终要在项目中使用,产品设计要能够满足多方的需求,如:满足开发人员的需求:开发快、易部署、易调试、易扩展;满足客户:界面美观、操作简单、流程合理、稳定坚挺不宕机;满足运维人员:操作简单、日志层次清晰、易定位、能够快速恢复、调优等等。 产品经理不但需要知道哪些需求能满足产品的目标,还要深刻的分析哪些需求应该做,哪些需求不应该做,这些需求优先级是什么,应该如何开展、进度规划,以及相关资源协调(美工人员、开发人员、测试人员)等。 4.2.2 产品研发 俗话说的好“打铁需要自身过硬”,作为产品经理对于技术能力的要求高于项目经理,对于代码有较高的敏感度,能够从全局来出发把控产品的可扩展性、可维护性等。 作为产品经理是整个团队的技术依靠,负责解决产品研发过程中的技术攻关以及“疑难杂症”,对新技术知识的要能够快速学习能力、掌握、运用。 在产品研发的过程难免会有迭代完善的过程,通常程序出错是常见的事情,产品经理要能够对于产品运行机制了然于心、代码排错能力要技高一筹。 4.2.3 技术支撑 产品经理需要对相关技术人员(内部、外部)提供技术支持,包括:产品培训、技术支持、BUG修复等。 产品经理需要对同行产品以及解决方案熟悉了解,能够充当售前协作市场人员进行产品宣讲、方案介绍等。 产品经理需要对前沿技术有高度敏感性、对技术来龙去脉、发展趋势有准确认知,能够作为公司内部技术人员的布道者,推进公司技术持续前进。 5 心得与体会 笔者作为数通畅联的一名技术人员,也曾担任过几个项目的项目经理,最近也参与对公司产品的完善调整、产品升级工作,故此将做项目与做产品的区别进行总结与大家分享。笔者认为项目是满足特定人群或者使用者需求,更偏向于个性化;而产品是满足特定应用场景或者特定行业领域的需求,更加具有兼容性。不管是项目经理、还是产品经理都要时刻提醒自己是负责人,尽职尽力,团队的事情就是自己的事情,拒绝鸵鸟心态,逃避不能解决问题,需要勇于面对问题,积极解决问题。 无论是做项目经理还是做产品经理自身的能力的提升是必不可少的,做事要目标驱动,明确任务的优先级,紧急有重要的事情优先做,重要不紧急的一直做,然后做紧急但不重要的,不重要也不紧急的最后做。贵在坚持,没有量的积累无法实现质的飞越,从0到70分很容易,从70到80分只要稍微努力,从80到90分就很困难了,从90到95分便更加困难,最终能达到95-99分更是寥寥无几。在职场中的生存就是“逆水行舟,不进则退”,面对日益激烈的竞争环境只有不断的提升自身的稀缺性,努力向前航行才可以到达成功的远方。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 我在2017年5月加入饿了么的交易部门,先后负责搜索、订单、超时、赔付、条约、交付、金额计算以及评价等系统,后期开始做些整体系统升级的工作。 这篇文章成型于交易系统重构一期之后,主要是反思其过程中做决策的思路,我没有使用「架构」这个词语,是因为它给人的感受充满权利和神秘感,谈论「架构」让人有一种正在进行责任重大的决策或者深度技术分析的感觉。 如毕玄在系统设计的套路这篇文章里所提: 回顾了下自己做过的几个系统的设计,发现现在自己在做系统设计的时候确实是会按照一个套路去做,这个套路就是:系统设计的目的->系统设计的目标->围绕目标的核心设计->围绕核心设计形成的设计原则->各子系统,模块的详细设计 在进行系统设计时,摸清楚目的,并形成可衡量的目标是第一步。 "Soft" ware Software拆开来分别是soft ware,即灵活的产品 -- 鲍勃大叔 重构前的交易系统第一版的代码可以追溯到8年前,这期间也经历过拆解重构,17年我来到时,主要系统是这样: 这套系统驮着业务从百万级订单跑到了千万级订单,从压测表现来看,它可以再支撑业务多翻几倍的量,也就是说如果没有啥变化,它可以继续稳定运行着,但如果发生点变化呢,答案可能就不这么肯定了。 在我入职的这两年里,系统承载的业务迭增变化:从单一的餐饮外卖到与新零售及品牌餐饮三方并行,又从到家模式衍生至到店,随之而来的是业务持续不断的差异化定制,还有并行上线的要求。另一面,随着公司组织架构变化,有的项目需要三地协同推进才能完成,沟通协作成本翻倍提升。几方面结合起来,导致开发没有精力对大部分系统的演进都进行完善的规划。 几个月前,业务提了一个简单的需求:对交易的评价做自动审核并进行相应的处罚。当时评价核心“域模型”是这样的: 设计自身的优劣这里暂不进行讨论,只是举例说明为了满足这个诉求,会涉及多个评价子模块要改动,开发评估下来的工作量远远超出了预期,业务方对此不满意,类似的冲突在其他系统里也经常出现。但实际上,团队里没人偷懒,和之前一样努力工作,只是不管投入了多少个人时间,救了多少次火,加了多少次班,产出始终上不去,因为开发大部分时间都在系统的修修补补上,而不是真正完成实际的新功能,一直在拆东墙补西墙,周而往复。 为什么会导致这样的结果,我想应该是因为大部分系统已经演变到很难响应需求的变更了,业务认为的小小变更,对开发来说都是系统的一次大手术,但系统本不应该往这个方向发展的,它和hardware有着巨大的区别就在于:变更对软件来说应该是简单灵活的。 所以我们思考设计的核心目标:**“采用好的软件架构来节省项目构建和维护的人力成本,让每一次变更都短小简单,易于实施,并且避免缺陷,用最小的成本,最大程度地满足功能性和灵活性的要求”。 Source code is the design 提到软件设计,大家脑袋里可能会想到一幅幅结构清晰的架构图,认为关于软件架构的所有奥秘都隐藏在图里了,但经历过一些项目后发现,这往往是不够的。Jack Reeves在1992年发表了一篇论文《源代码即设计》,他在文中提出一个观点: 高层结构的设计不是完整的软件设计,它只是细节设计的一个结构框架。在严格地验证高层设计方面,我们的能力是非常有限的。详细设计最终会对高层设计造成的影响至少和其他的因素一样多(或者应该允许这种影响)。对设计的各个方面进行改进,是一个应该贯穿整个设计周期的过程 在踩过一些坑之后,这种强调详细设计重要性的观点在我看来很实在接地气,简单来说:“自顶向下的设计通常是不靠谱的,编码即是设计过程的一部分”,个人认为:系统设计应该是从下到上,随着抽象层次的提升,不断演化而得到良好的高层设计。 编程范式 从下向上,那就应该从编码开始审视,饿了么交易系统最开始是由Python编写,Python足够灵活,可以非常快速的产出mvp的系统版本,这也和当时的公司发展状态相关: 产品迭代迅速,新项目的压力很大。 最近这次重构,顺应集团趋势,我们使用Java来进行编写,不过在这之前有一个小插曲:17年底,因为预估到当前系统框架在单量到达下一个量级时会遇到瓶颈,所以针对一些新业务逐渐开始使用Go语言编写,但在这个过程里,经常会听到一些言论:用go来写业务不舒服。为什么会不舒服?大致是因为没有框架,没有泛型,没有try catch,确实,在解决业务问题的这个大的上下文中,go语言不是最优的选择,但语法简单,可以极大程度的避免普通程序员出错的概率。 那么Python呢,任何事物都有双刃剑,虽然Python具有强表达力,但是灵活性也把很多人惯坏了,代码写的糙,动态语言写太多坑也多,容易出错,在大项目上的工程管理和维护上有一定劣势,所以rails作者提到:“灵活性被过分高估——约束才是解放”也有一定道理 为避免引起语言战,这里不过多讨论,只是想引出:我从C++写到Go,又从Python写到Java,在这个过程里体会到--编程范式也许是学习任何一门编程语言时要理解的最重要的术语,简单来说它是程序员看待程序应该具有的观点,但却容易被忽视。交易老系统的代码,不管是针对什么业务逻辑,几乎都是OPP一杆到底,类似的代码在系统里随处可见。 我们好像完全遗忘了OOP,这项古老的技艺被淡化了,我这里不是说一定要OOP就是完美的,准确来说我是“面向问题”范式的拥趸者,比如,Java从骨子里就是要OOP,但是业务流程不一定需要OOP。一些交易业务就是第一步怎么样,第二步怎么样,采取OPP的范式就是好的解法。这时,弄很复杂的类设计有时并不必要,反而还会带来麻烦 此外,同一个问题还可以拆解为不同的层次,不同的层次可以使用各自适合的方式。比如高层的可以OOP,具体到某个执行逻辑里可以用FP,比如:针对订单的金额计算,我们用Go写了一版FP的底层计算服务,性能高、语法简单以及出错少等是语言附带的优点,核心还是因为该类问题自身适合。 然而,当面向整个交易领域时,针对繁复多样的业务场景,合理运用OOP的设计思想已经被证明确实可以支撑起复杂庞大的软件设计,所以我们作出第一个决策:采用以OOP为主的“混合”范式。 原则和模式 The difference between a bad programmer and a good one is whether he considers his code or his data structures more important. Bad programmers worry about the code. Good programmers worry about data structures and their relationships. -- Linus Torvalds 不管是采用哪种编程范式、编程语言,构造出来的基础模块就像盖楼的砖头,如果砖头质量不好,最终大楼也不会牢固,引用里的一大段话,relationships才是我最想强调的:我理解它是指类之间的交互关系,“关系”的好坏通常等价于软件设计的优劣,设计不好的软件结构大都有些共同特征: 僵化性:难以对软件进行改动,一般会引发连锁改动,比如下单时增加一个新的营销类型,订单中心和相关上下游都要感知到并去做改动 脆弱性:简单的改动会引发其他意想不到的问题,甚至概念完全不相关 牢固性:设计中有对其他系统有用的部分,但是拆出来的风险和成本很高,比如订单中心针对外卖场景的支付能力并不能支持会员卡等虚拟商品的支付需求 不必要的复杂性:这个通常是指过度设计 晦涩性:随时间演化,模块难以理解,代码越来越难读懂,比如购物车阶段的核心代码已经长成了一个近千行的大函数 ... 采取合适的范式后,我们需要向上抽一个层次,来关注代码之上的逻辑,多年软件工程的发展沉淀下来了一些基本原则和模式,并被证明可以指导我们如何把数据和函数封装起来,然后再把它们组织起来成为程序。 SOLID 有人将这些原则重新排列下顺序,将首字母组成SOLID,分别是:SRP、OCP、LSP、ISP、DIP。这里针对其中几个原则来举些例子。 SRP(单一职责):这个原则很简单,即任何一个软件模块都应该只对一类用户负责,所以代码和数据应该因为和某一类用户关系紧密而被组织到一起。实际上我们大部分的工作就是在发现职责,然后拆开他们。 我认为该原则的核心在于用户的定义,18年去听Qcon时,听到俞军的分享,其中一段正好可以拿来诠释什么是用户,俞军说:“用户不是人,是需求的集合”。在我们重构的过程中,曾经对交易系统里的交付环节有过争论,目前饿了么支持商家自配和平台托管以及选择配送(比如跑腿),这几类配送的算价方式,配送逻辑,和使用场景都不一样,所以我们基于此做了拆解,一开始大家都认同这种分解方式。 但后来商户群体调整了,新零售商户和餐饮商户进行分拆,对应着业务方的运营方式也开始出现差异,导致在每个配送方式下也有了不同诉求,伴随这些变化,最后我们选择做了第二次拆解。 对于单一职责,这里有个小tips:大家如果实在不好分析的话,可以多观察那些因为分支合并而产生冲突的代码,因为这很可能是因为针对不同需求,大家同时改了同一个模块。 DIP(依赖倒置):有人说依赖反转是OOP和OPP的分水岭,因为在过程化设计里所创建的依赖关系,策略是依赖于细节的--也就是高层依赖于底层,但这通常会让策略因为细节改变而受到影响,举个例子:在外卖场景下,一旦用户因为某些原因收不到餐了,商户会赔代金券安抚用户,此时OPP可以这样做: 而过一阵子,因为代金券通常不能跨店使用,平台想让用户继续复购,就想通过赔付通用红包来挽留,这个时候就需要改动老的代码,通过增加对红包赔付逻辑的依赖,才可以来满足诉求。 但如果换个方式,采用DIP的话,问题也许可以被更优雅的解决了: 当然这个示例是简化后的版本,实际工作里还有很多更加复杂的场景存在,但本质都是一样:采用OOP倒置了策略对细节的依赖,使细节依赖于抽象,并且常常是客户拥有服务接口,这个过程中的核心是需要我们做好抽象。 OCP(开闭原则):如果仔细分析,会发现这个原则其实是我们一开始定的系统设计的目标,也是其他原则最终想达成的目的,比如:通过SRP,把每个业务线的模块拆解出来,将变动隔离,但是平台还要做一定的抽象,将核心业务流程沉淀下来,并开放出去每个业务线自己定义,这时候就又会应用到DIP。 其他的几个原则就不举例子了,当然除了SOLID,还有其他类型的原则,比如IoC:用外卖交易平台举例子,商户向用户卖饭,一手交钱一手交货,所以,基本上来说用户和商户必需强耦合(必需见面)。这个时候,饿了么平台出来做担保,用户把钱先垫到平台,平台让商家接单然后出餐,用户收到餐后,平台再把钱打给商家。这就是反转控制,买卖双方把对对方的直接依赖和控制,反转到了让对方来依赖一个标准的交易模型的接口。 可以发现只要总结规律,总会出现这样或那样的原则,但每个的原则的使用都不是一劳永逸的--需要不断根据实际的需求变化做代码调整,原则也不是万金油,不能无条件使用,否则会因为过分遵循也会带来不必要的复杂性,比如经常见到一些使用了工厂模式的代码,里面一个new其实就是违反了DIP,所以适度即可。 演进到模式 这里的模式就是我们常说的设计模式,用演进这个词,是因为我觉得模式不是起点,而是设计的终点。《设计模式》这本书的内容不是作者的发明创造,而是其从大量实际的系统里提取出来的,它们大都是早已存在并已经广泛使用的做法,只不过没有被系统的梳理。换句话说,只要遵循前面叙述的某些原则,这些模式完全可能会自然在系统代码中体现出来,在《敏捷软件开发》这本书里,就特意有一个章节,描述了一段代码随着调整慢慢演进到了观察者模式的过程。 拥有模式固然是好的,比如搜索系统里,通过Template Method模式,定义一套完整的搜索参数解析模版,只需要增加配置就可以定制不同的查询诉求。这里最想强调的是不要设计模式驱动编程,拿交易系统里的状态机来举例子(状态机简直太常见了,简单如家里使用的台灯,都有一个开和关的状态,只是交易场景下会更加复杂),在餐饮外卖交易有如下的状态流转模型: 实现这样的一个有限状态机,最直接的方式是使用嵌套switch/case语句,简略的代码比如: public class Order { // States public static final int ACCEPT = 5; public static final int SETTLED = 9; .. // Events public static final int ARRIVED = 1; // 订单送达 public void event(int event) { switch (state) { case ACCEPT: switch (event) { case ARRIVED: state = SETTLED; //to do action break case } } } } 因为是简写了流程,所以上面的代码看起来还是挺能接受的,但是对于订单状态这么复杂的状态机,这个switch/case语句会无限膨胀,可读性很差,另一个问题是状态的逻辑和动作没有拆开,《设计模式》提供了一个State 模式,具体做法是这样: 这个模式确实分离了状态机的动作和逻辑,但是随着状态的增加,不断增加State的类会让系统变得异常复杂,而且对OCP的支持也不好:对切换状态这个场景,新增类会引起状态切换类的修改,最不能忍受的是这个方式会把整个状态机的逻辑隐藏在零散的代码里。 旧版的交易系统就使用的是解释迁移表来实现的,简化版本是这样的: # 完结订单 add_transition(trigger=ARRIVED, src=ACCEPT, dest=SETTLED, on_start=_set_order_settled_at, set_state=_set_state_with_record, // 变更状态 on_end=_push_to_transcore) ... # 引擎 def event_fire(event, current_state): for transition in transitions: if transition.on_start == current_state && transition.trigger == event: transition.on_start() current_state = transition.dest transition.on_end() 这个版本非常容易理解,状态逻辑集中在一起,也没有和动作耦合起来,扩展性也比较强,唯一缺点的话是遍历的时间,但也可以通过字典表来优化,但它总体带来的好处更加明显。 不过随着业务发展,交易系统需要同时支持多套状态机,意味着会出现多个迁移表,而且还有根据业务做扩展定制的需求,这套解决方案会导致代码编写变得复杂起来,我们在重构时采用了二级编排+流程引擎的方式来优化了这个问题,只是不在我们讨论的范围内,这里只想强调第二个决策:代码上要灵活通过设计原则分析问题,再通过合适的设计模式解决问题,不能设计模式驱动编程,比如有时候一个全局变量就可以替代所谓的单例模式。 丰富的领域含义 一旦你想解说美,而不提拥有这种特质的东西,那么就完全无法解释清楚了 用个不那么贴切的说法,如果前面说的是针对静态问题的策略,现在我们需要讨论面对动态问题的解决办法:即使没有风,人们也不会觉得一片树叶是稳定的,所以人们定义稳定的时候和变更的频繁度无关,而是和变更需要的成本有关,因为吹一口气,树叶就会随之摇摆了。我们除了要写好当前代码,让其足够清晰合理,还要能写好应对需求变化的“树叶”代码。 面向业务变化的设计首先就是要理解业务的核心问题,进而进行拆解划分为各个子领域,DDD--也就是领域驱动设计,已经被证明是一个很好的切入点。这里不是把它当作技术来学习,而是作为指导开发的方法论,成为第三个决策,并且我个人仍处在初级阶段,所以只说一些理解深刻的点。 通用语言 设计良好的架构在行为上对系统还有一个最重要的作用:就是明确的显式的反映系统设计的意图,简单来说,在你拉下某些服务的代码的时候,大概扫一眼就可以觉得:嗯,这个“看起来” 就像一个交易系统的应用。我们不能嘴上在谈论业务逻辑,手上却敲出另一份模样的代码,简单来说,不能见人说人话,见鬼说鬼话。可以对比一下这两类分包的方式,哪一个更容易理解:
发现领域通用语言的目的之一是可以通过抓住领域内涵来应该需求变更,这个需要很多客观条件,比如团队里有一个领域专家。但没有的时候,我们也可以向内求解,**我有次看到一位在丁香园工作的程序员朋友,购买了一大批医学的书籍,不用去问,我就猜他一定是成了DDD的教徒。 针对这个点,我们这次重构时还做了些让“源代码即设计”的工作:领域元素可视化,当系统领域内的一些概念已经和产品达成一致之后,便增加约定好的注解,代码编译时便可以扫描并收集起来发送给前端,用于画图。 回到前面提到的评价域模型,后来在和产品多次沟通后意识到,产品没有希望评价这么多种类,对它来说商品也好、骑手也好,都属于被评价的对象,从领域模型来看,之前的设计更多是面对场景,而不是面对行为,所以合理的域模型应该是: 限界上下文 这个在我们平时开发过程中会很常见。拿用户系统举例:一个User的Object,如果是从用户自身的视角来看,就可以登陆、登出,修改昵称;如果是从其他普通用户来看,就只能看看昵称之类的;如果从后台管理员来看,就可以注销或者踢出登陆。这时就需要界定一个Scope,来说明现在的User到底是哪个Scope,这其实就是DDD中限界上下文的理念。 限界上下文可以很好的隔离相同事物的不同内涵,通过严格规范可以进入上下文的对象模型,从而保护业务抽象行为的一致性,回到交易领域,饿了么是最开始支持超级会员玩法的,为了支持对应的结算诉求,需要接入交易系统来完成这个业务,我们通过分解问题域来降低复杂度,这个时候就对应切割为会员域和交易域,为了保护超会卡在进入交易领域的时候,不扰乱交易内部的业务逻辑,我们做了一次映射: 切分 当所有代码完成之后,随着程序增长,会有越来越多的人参与进来,为了方便协作,就必须把这些代码划分成一些方便个人或者团队维护的组。根据软件变更速度不同,可以把上文提到的代码化为几个组件: Extension:扩展包,这里存放着前面提到的业务定制包,面向对象的思想,最核心的贡献在于通过多态,允许插件化的切换一段程序的逻辑,其实软件开发技术发展的历史就是一个想法设法方便的增加插件,从而创建一个可扩展,可维护的系统架构的过程。 Domain: 领域包,存放着具备领域通用语言的核心业务包,它最为稳定。 Business:业务包,存放着具体的业务逻辑,它和Domain包的区别在于,可能Domain包会提供一个people.run()的方法,他会用这个方法去跑着送外卖,或者去健身。 Infra: 基础设置包,存放这对数据库及各种中间件的依赖,他们都属于业务逻辑之外的细节。 然后是分层依赖,Martin Flower已经提供了一套经典的分层封装的模式,拿简化的订单模块举例: 然而如果有的同学避免做各种类型的转换,不想严格遵守分层依赖,觉得一些查询(这里指Query,Query != Read)可以直接绕过领域层,这样就变成了CQRS模式: 但是最理想的还是下面这种方式,领域层作为核心业务逻辑,不应该依赖基础设施的细节,通过这种方式,代码的可测性也会提升上去 单体程序的组件拆分完毕后,再向上一层,我们开始关注四个核心服务:Booking被分拆为Cart、Buy、Calculate,Eos被分拆为Procee、Query、Timeout,Blink一部分和商户订单相关的功能被分拆到Process、Query,和物流交付的部分单独成一块Delivery,最后交易的核心服务拆解成下图: 到目前,算上这个切分的方式,加起来一共就四个决策,其实也没必要分序列,它们核心都是围绕着软件灵活性这个目标,从程序范式到组件编写,最后再到分层,我们主动选择或避开的一些教条限制,所以业务架构从某种意义上来讲,也是在某种领域中限制程序员的一些行为,让他往我们所希望的规范方向编码。从而达到整个系统的灵活可靠。 "No Silver Bullet" “个体和交互胜过过程和工具”,敏捷宣言第一条 目前系统架构是什么样子并不重要,因为它可能会随着时间还会拆解成其他模样,重要的是,我们要认识到对于如何建造一个灵活的交易系统——没有银弹。 如果仔细观察的话,会发现当前系统里仍有很多问题等着被解决。比如一些横跨型变更:系统链路里会因为某个服务的接口增加了字段,而导致上下游跟着一起改。更为尴尬的是,本来我们拆分服务就是为了解耦合,但有时还会出现服务发布依赖的现象。系统演进是一场持久的战争,“个体和交互胜过过程和工具”,人才是胜利的核心因素。 过去的两年里,我们没有停止过思考和实践,经常可以看到交易团队内部成员的争执,小到一个接口字段变更,大到领域之间的边界,大家为拿到一个合理的技术方案做了很多讨论,这让我想起《禅与摩托车维修艺术》里所提到的良质,有人点评说:关于良质,程序员可能有这样的经历——写出了一段绝妙的代码,你会觉得“不是你写出了代码,这段代码一直存在,而你发现了它”。 本文作者:盛赫,花名白茶,就职于阿里本地生活中台研发部,多年交易系统建设开发经验,目前转入营销领域继续探索。 参考书籍 《软件设计的哲学》--John Ousterhout 《禅与摩托维修艺术》--Robert M.Pirsig 《领域驱动设计》--Eric Evans 《敏捷软件开发》--Uncle Bob 《架构整洁之道》--Uncle Bob 《极客与团队》--Brian W.FItzapatrick 原文链接 本文为云栖社区原创内容,未经允许不得转载。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 录制音频是我们日常生活中很容易用到的工具,有时候用电脑看视频时,听到喜欢的音乐想要录制下来。现在我们都知道网络上下载听歌很多都要版权,会让你冲钱,所以录制音乐是可以保存歌曲的一个方法,录制视频音乐的方法?用什么软件?跟着小编一起来录制吧! 首先我们需要安装软件,可以在自己的电脑上下载迅捷录音软件,安装到我们的电脑上,方法非常简单,只需要在迅捷官网上点击下载,安装即可。安装好以后我们来了解具体的录制方法。 1、双击打开自己电脑上的迅捷录音软件,打开后软件里有很多功能可以用,我们点击“格式选项”这个工具,选择左边的倒三角,把格式选择为MP3格式。 2、然后设置“声音来源”,这里我们选择“仅系统声音”,这个选项的意思是只录制我们电脑发出的声音,就是只录制我们电脑视频发出的声音。 3、选择保存位置,点击“更改目录”点击打开后跳转到设置界面,把目录保存位置设置到桌面,这样我们录制完成的音频就会保存到桌面了。 4、这时候我们点击打开要录制的视频,然后开始播放视频,等待视频播放到自己要录制的音乐节点时,点击迅捷录制软件的开始录制。等待录制完成后,点击右下方的结束按钮,想要录制的音频文件就完成了。 以上就是小编为大家分享的录制视频音乐的方法?用什么软件?的解读,录制视频音乐方法很简单,只需要大家下载安装迅捷录音软件就可以轻松实现,希望可以帮助到有需要的朋友。 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 典型的OKR系统周期 常见的OKR系统周期为: 1)在年初,公司定义了一组高级战略OKR,最好是在团队的帮助下。 重要的是要理解,没有团队的投入,高层管理人员就不应孤立地制定战略性OKR。Keith R. McFarland 在他的文章标题: 您应该像构建软件一样构建战略吗? 由于组织中各个级别的人员都会进行日常折衷,从而影响公司的战略成功,因此需要设计流程来吸收组织各个方面的想法,而不仅仅是高层管理人员。 2)执行团队然后验证公司的 OKR ,并从团队中收集反馈。 3)团队使用上述双向方法开发战术OKR。 4)团队映射相互依存关系,并确保与其他团队和计划保持一致。 5)团队每周签到一次,以跟踪结果和行动。 6)对于使用季度 OKR的 公司,通常在中期OKR审查期间在季度的中途对OKR进行审查。 7)在周期结束时,您可以快速回顾一下/吸取教训并重新开始。 进行回顾的最简单方法是 开始-停止-继续 格式。在此模型中,要求每个团队成员标识团队应做的特定事情:开始做/停止做/继续做。 对上一个周期未实现的OKR进行重新评估,以便可以将它们包含在下一个季度中,如果不再需要,可以将其丢弃。 一些公司将目标视为公司和团队随时间推移而追求的"愿景",因此目标可能会从一个季度过渡到下一个季度。例如,诸如"使我们的客户满意"之类的目标是公司可以在多个季度中使用的目标,在每个战术周期中创建新的关键结果。 甚至随着时间的推移,某些关键结果本身也可能是相同的,只是更改了目标。在我所见过的所有公司中,几乎所有季度都有收入和净促销值等指标。但是,每个团队将用来改善这些指标的价值驱动力会随着时间而变化。 为什么您应该将OKR和薪酬分开 OKR是一种管理工具,而不是员工评估工具。因此,OKR框架的基本组成部分是将OKR与薪酬和晋升分开。 正如英特尔的安迪-格鲁夫(Andy Grove)写道: OKR不是作为绩效评估基础的法律文件,而应仅是用于确定个人表现如何的一项输入。 Google的Rick Klau写道: OKR不是员工评估的代名词。OKR与公司的目标以及每个员工如何为这些目标做出贡献有关。绩效评估(完全是评估员工在给定期间的绩效)应独立于其OKR。 这与显示老化迹象的传统模型有很大不同。Willis Towers Watson进行的一项 研究 表明,绩效工具的典型薪酬既不能有效地提高个人绩效,也无法对其进行奖励: 北美只有20%的雇主表示,绩效工资有效地推动了组织中更高水平的个人绩效。 公司对短期激励给予低分。只有一半的人说短期激励措施可以有效地提高个人绩效,而更少的受访者(47%)说这些激励措施可以有效地根据个人绩效来区分薪酬。 两个奖金的故事 曾经有一个组织有两个员工在同一团队中:保罗和玛丽。 保罗很聪明,专注并取得了成果。但是他受到金钱奖励的驱使,并且一直试图找出如何赚更多的钱。 玛丽也很聪明,专心,但是她为自己的成就所驱动。她相信,如果她成功了,金钱将会随之而来。 该组织使用简化的奖金公式,将目标与奖励联系起来: 奖金=ƒ(已达成目标的百分比*薪水等级) 这意味着奖金的大小是员工薪资等级和员工实现目标的百分比的函数。 然后,发生了以下情况: 保罗实现了一个轻松目标的110%,在与经理进行了数轮谈判之后,他成功地实现了目标。 玛丽达到了一个雄心勃勃的目标的80%,远远超出了公司认为可能的范围。一个真正的伸展目标。 谁应该得到更高的奖金?当然是玛丽。 但是,谁最终得到了更大的奖金?保罗 这个故事是不正当动机的经典例子。实际上,我们的奖励制度是对不当行为的奖励。 我们都是保罗和玛丽 每个人里面都有一些保罗和玛丽。您的激励系统应该对现实生活中的真实人起作用。即使您的团队充满玛丽,为什么您会拥有一个激励您不想发生的事情的系统? 如果您要创建一种文化,以制定延伸目标为准则,则应考虑放弃针对奖金和晋升的基于公式(或紧密耦合)的模型。 有什么选择? 另一种选择是采用一种系统,其中将目标的实现输入到绩效评估过程中,其中定义了奖金和晋升。在此模型中,奖金和目标是松散耦合的。 绩效评估不仅考虑实现目标的百分比,而且还考虑目标本身:难度和对业务的影响。将其视为体操难度分数:执行更困难的例程可以获得更多分数。 "但是这太主观了" 关于此模型的常见抱怨之一是它是"主观的",而基于公式的模型是"客观的"。 问题在于,在流程结束时使用公式并不能使其客观。人们认为这是客观的,因为他们所看到的只是一点数学: 世界各地的几家公司(至少有时)使用即期奖金或酌情性奖金来补偿或补充奖金政策。两者都是遵循主观规则的100%任意性; 根据谁拥有最好的谈判技巧来减少目标来计算奖金是"主观的"; 项目/资源分配是任意的。有时,组织需要有人来解决一个陷入困境的项目,这可能会在短期内损害他/她的奖金-通常由即期奖金来弥补。 与moonshots一样,我强烈建议您一开始不要采用这种模式。在组织中拥有稳定成熟的OKR功能之前,请勿更改您的薪酬模型。 那销售配额呢? 销售团队有所不同,因为结果更易于衡量。您可以将奖金附加到销售配额中,但是您应该避免使用任何奖励谈判减少配额的员工的模型。 常见的OKR错误 从最基本的错误开始,这些是我们在OKR实现中遇到的最常见的错误: 设置不可衡量的关键结果: 记住约翰-多尔的公式。每个关键结果必须是可测量的。 过多的 OKR 或主要结果: OKR并不是您所做的所有事情的清单。它代表了您的首要任务。少即是多。 将任务包括为关键结果: 关键结果不是您要做的事情。这是您所做的成功的结果。 自上而下设置OKR: OKR不级联。相信您的团队,并帮助他们了解他们如何做出贡献。 在筒仓中创建OKR: 设置OKR时,团队必须互相交谈,否则将无法达成一致。 " 一劳永逸 ": 不要将您的OKR视为新年的决议。OKR必须成为您组织文化的一部分,并且必须定期进行跟踪。 在补偿公式中包括OKR: OKR不是员工评估工具。OKR是一种管理工具。 试图盲目复制Google: 采用OKR并非只有一种方法。即使在Google内部,不同的团队也以多种方式使用OKR。了解所涉及的原理,并使您的实施适应组织的需要。 你可能还想看: 敏捷与OKR实践(如何让OKR与敏捷计划共存) Google OKR指导手册 有赞商城的OKR实践分享 OKR新手入门指南系列 OKR新手入门指南 第一部分 OKR新手入门指南 第二部分 OKR新手入门指南 第三部分 OKR新手入门指南 第四部分 作者: Felipe Castro 译者: Bob Jiang 原文链接 绩效管理 OKR 谷歌 目标系统 本文首发于 Bob Jiang的博客 ,转载请联系 Bob Jiang 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 工欲善其事,必先利其器。本文收集 IntelliJ IDEA 常用插件,持续更新中...... Alibaba Java Coding Guidelines 阿里巴巴《Java 开发手册》配套插件,可以实时检测代码中不符合手册规约的地方,助你码出高效,码出质量。 使用: 当我们违反手册规约时,该插件会自动检测并进行提示。 同时提供了一键检测所有代码规约情况和切换语言的功能。 如果你想修改某条规约的检测规则的话,可以通过设置的 Editor -> Inspections 进行修改。 Easy Code EasyCode 用于 代码自动生成 ,支持模板自定义、导入、导出,方便团队之间共享。 介绍: 基于 IntelliJ IDEA 开发的代码生成插件,支持自定义任意模板(Java,html,js,xml)。 只要是与数据库相关的代码都可以通过自定义模板来生成。支持数据库类型与 java 类型映射关系配置。 支持同时生成生成多张表的代码。每张表有独立的配置信息。完全的个性化定义,规则由你设置。 具体使用见: IntelliJ IDEA 插件 EasyCode(代码自动生成) Lombok Lombok 为 Java 项目提供了非常有趣的附加功能,使用它的注解可以有效的地解决那些繁琐又重复的代码,例如 Setter、Getter、toString、equals、hashCode 以及非空判断等。 使用: 我们给一个类添加 @Getter 和 @Setter 注解, Lombok 就会为我们自动生成所有属性的 Getter 和 Setter 方法。 Free MyBatis plugin MyBatis 扩展插件,可以在 Mapper 接口的方法和 xml 实现之间自由跳转,也可以用来一键生成某些 xml 实现。 介绍: 生成 mapper xml 文件。 快速从代码跳转到 mapper 及从 mapper 返回代码。 mybatis 自动补全及语法错误提示。 集成 mybatis generator gui 界面。 使用: 我们可以通过 Mapper 接口中方法左侧的箭头直接跳转到对应的 xml 实现中去。 也可以从 xml 中 Statement 左侧的箭头直接跳转到对应的 Mapper 接口方法中去。 还可以通过 Alt+Enter 键组合直接生成新方法的 xml 实现。 MyBatis Log Plugin 有时候我们需要运行过程中产生的 SQL 语句来帮助我们排查某些问题,这款插件可以把 Mybatis 输出的 SQL 日志还原成完整的 SQL 语句,就不需要我们去手动转换了。 使用: 首先我们需要打开这款插件的窗口。 当我们调用方法,控制台输出 Mybatis 的 SQL 日志时,该插件会自动帮我们转换成对应的 SQL 语句。 有的时候我们需要转换的日志并不在自己的控制台上,这时可以使用插件的 SQL Text 功能:直接复制我们需要转换的日志,然后点击 Restore Sql 按钮即可。 RestfulToolkit 一套 Restful 服务开发辅助工具集,提供了项目中的接口概览信息,可以根据 URL 跳转到对应的接口方法中去,内置了 HTTP 请求工具,对请求方法做了一些增强功能,总之功能很强大! 介绍: 根据 URL 直接跳转到对应的方法定义 ( Ctrl \ or Ctrl Alt N ); 提供了一个 Services tree 的显示窗口; 一个简单的 http 请求工具; 在请求方法上添加了有用功能: 复制生成 URL;复制方法参数... 其他功能: java 类上添加 Convert to JSON 功能,格式化 json 数据 ( Windows: Ctrl + Enter; Mac: Command + Enter )。 使用: 可以通过右上角的 RestServices 按钮显示项目中接口的概览信息。 可以通过搜索按钮,根据 URL 搜索对应接口。 可以通过底部的 HTTP 请求工具来发起接口测试请求。 通过在接口方法上右键可以生成查询参数、请求参数、请求 URL 。 通过在实体类上右键可以直接生成实体类对应的 JSON 。 Translation 一款翻译插件,支持 Google、有道、百度翻译,对我们看源码时看注释很有帮助! 使用: 直接选中需要翻译的内容,点击右键即可找到翻译按钮。 直接使用翻译文档可以将整个文档都进行翻译。 还可以通过右上角的翻译按钮直接翻译指定内容。 GsonFormat 这款插件可以把 JSON 格式的字符串转化为实体类,当我们要根据 JSON 字符串来创建实体类的时候用起来很方便。 使用: 首先我们需要先创建一个实体类,然后在类名上右键 Generate ,之后选择 GsonFormat 。 输入我们需要转换的 JSON 字符串。 选择性更改属性名称和类型。 点击确定后直接生成实体类。 Grep Console 一款帮你分析控制台日志的插件,可以对不同级别的日志进行不同颜色的高亮显示,还可以用来按关键字搜索日志内容。 使用: 当项目打印日志的时候,可以发现不同日志级别的日志会以不同颜色来显示。 如果你需要修改配色方案的话,可以通过 Tools 打开该插件的配置菜单,然后通过配置菜单修改配色方案。 可以通过在控制台右键并使用 Grep 按钮来调出日志分析的窗口。 然后直接通过关键字来搜索即可。 Maven Helper 解决 Maven 依赖冲突的好帮手,可以快速查找项目中的依赖冲突,并予以解决! 使用: 我们可以通过 pom.xml 文件底部的依赖分析标签页查看当前项目中的所有依赖。 通过冲突按钮我们可以筛选出所有冲突的依赖。 选中有冲突的依赖,点击 Exclude 按钮可以直接排除该依赖。 同时 pom.xml 中也会对该依赖添加 标签。 Statistic 一款代码统计工具,可以用来统计当前项目中代码的行数和大小。 使用: 我们可以通过顶部菜单中的 View -> Tool Windows -> Statistic 按钮开启该功能。 此时就可以看到我们项目代码的统计情况了。 Vue.js Vue.js 支持插件,写过前端的朋友肯定用过,可以根据模板创建 .vue 文件,也可以对 Vue 相关代码进行智能提示。 使用: 启用该插件后,可以根据模板新建 .vue 文件。 当我们在标签中写入以 v- 开头的代码时,会提示 Vue 中的相关指令。 element Element-UI 支持插件,可以对 Element-UI 中的标签进行智能提示,有了它就不用盲写相关代码了! 使用: 当我们写入以 el- 开头的标签时,会提示 Element-UI 相关组件。 扫码关注微信公众号 程序员35 ,获取最新技术干货,畅聊 #程序员的35,35的程序员# 。独立站点: https://cxy35.com
【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> fdisk -l 显示硬盘分区,找到待挂载的硬盘编号 df -l 显示当前硬盘挂载情况 mount -t ext4 /dev/sdb /devdata 将硬盘挂载到devdata目录 【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>> 你的电脑是否自己经常进入睡眠状态?小编今日推荐的 Caffeinated mac中文版 是Macos上一款系统防睡眠软件,Caffeinated Mac版是一个小巧但强大的应用程序,它可以让你轻松地覆盖你的节能设置,让你的Mac电脑保持清醒。 地址: https://www.macw.com/mac/583.html |