一线互联网技术总监的忠告:我们精通那么多技术为何还是做不好一个项目?(深度好文,值得收藏!

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

其实就是项目管理四要素——成本、进度、范围、质量,传统项目管理理论认为这四要素彼此制约难以兼得,项目管理的艺术在于四要素的平衡取舍。

关于软件工程和项目管理的理论和著作已经很多很成熟,这里我从程序员的视角提出一个新的观点——质量不可妥协

  • 质量要素不是一个可以被牺牲和妥协的要素——牺牲质量会导致其它三要素全都受损,反之同理,追求质量会让你在其它三个方面同时受益。

  • 在保持一个质量水平的前提下,成本、进度、范围三要素确确实实是互相制约关系——典型的比如牺牲成本(加班加点)来加快进度交付急需的功能。

  • 正如著名的“破窗效应”所启示的那样:任何一种不良现象的存在,都在传递着一种信息,这种信息会导致不良现象的无限扩展,同时必须高度警觉那些看起来是偶然的、个别的、轻微的“过错”,如果对这种行为不闻不问、熟视无睹、反应迟钝或纠正不力,就会纵容更多的人“去打烂更多的窗户玻璃”,就极有可能演变成“千里之堤,溃于蚁穴”的恶果——质量不佳的代码之于一个项目,正如一扇破了的窗之于一幢建筑、一个蚂蚁巢之于一座大堤。

  • 好消息是,只要把质量提上去项目就会逐渐走上健康的轨道,其它三个方面也都会改善。管好了质量,你就很大程度上把握住了项目成败的关键因素。

  • 坏消息是,项目的质量很容易失控,现实中质量不佳、越做越臃肿混乱的项目比比皆是,质量改善越做越好的案例闻所未闻,以至于人们将其视为如同物理学中“熵增加定律”一样的必然规律了。

  • 当然任何事情都有一个度的问题,当质量低于某个水平时才会导致其它三要素同时受损。反之当质量高到某个水平以后,继续追求质量不仅得不到明显收益,而且也会损害其它三要素——边际效用递减定律。

  • 这个度需要你为自己去评估和测量,如果目前的质量水平还在两者之间,那么就应该重点改进项目质量。当然,现实世界中很少看到哪个项目质量高到了不需要重视的程度。

3. 项目走向衰败的最常见诱因——代码质量不佳

===========================================================================================

一个项目的衰败一如一个人健康状况的恶化,当然可能有多种多样的原因——比如需求失控、业务调整、人员变动流失。但是作为我们技术人,如果能做好自己分内的工作——编写出可维护的代码、减少技术债利息成本、交付一个健壮灵活的应用架构,那也绝对是功德无量的。

虽然很难估算出这究竟能挽救多少项目,但是在我十多年职业生涯中,经历的和近距离观察的几十个项目,确实看到了大量的项目正是由于代码质量不佳导致的失败和遗憾,同时我也发现其实失败项目的很多问题、症结也确确实实都可以归因到项目代码的混乱和质量低下,比如一个常见的项目腐烂恶性循环:代码乱》bug 多》排查问题耗时》复用度低》加班 996》士气低落……

所谓“千里之堤,毁于蚁穴”,代码问题就是蚁穴。

接下来,让我们从项目管理聚焦到项目代码质量这个相对小的领域来深入剖析。编写高质量可维护的代码是程序员的基本修养,本文试图在代码层面找到一些失败项目中普遍存在的症结问题,同时基于个人十几年开发经验总结出的一些设计模式作为药方分享出来。

关于代码质量的话题其实很难通过一篇文章阐述明白,甚至需要一本书的篇幅,里面涉及到的很多概念关注点之间存在复杂微妙关系。

推荐《设计模式之美》的第二章节《从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?》,这是我看到的关于代码质量主题最精彩深刻的论述。

4. 一个失败项目复盘

===============================================================================

先贴几张代码截图,看一下这个重病缠身的项目的病灶和症状:

  • 这是该项目中一个最核心、最复杂也是最经常要被改动的 class,代码行数 4881;

  • 结果就是冗长的 API 列表(列表需要滚动 4 屏才能到底,公有私有 API 180 个);

image

  • 还是那个 Class,头部的 import 延绵到了 139 行,去掉第一行 package 声明和少量空行总共 import 引入了 130 个 class!

  • 还是那个坑爹的组件,从 156 行开始到 235 行声明了 Spring 依赖注入的组件 40 个!

这里先不去分析这个类的问题,只是初步展示一下病情严重程度。

我相信这应该不算是特别糟糕的情况,比这个严重的项目俯拾皆是,但是这也应该足够拿来暴露问题、剖析成因了。

4.1 症结 1:组件粒度过大、API 泛滥


分层的理念早已深入人心,尤其是业务逻辑层的独立,彻底杜绝了之前(不分层的年代)业务逻辑与展现逻辑、持久化逻辑等混杂的问题。

但是好景不长,随着业务的复杂和变更,在业务逻辑层的复杂性也急剧增加,成为了新的开发效率瓶颈, 问题就出在了业务逻辑组件的划分方式——按领域模型划分业务逻辑组件:

  • 业界关于如何设计业务逻辑层 并没有标准和最佳实践,绝大多数项目(我自己经历过的项目以及我有机会深入了解的项目)中大家都是想当然的按照业务领域对象来设计;

  • 例如:领域实体对象有 Account、Order、Delivery、Campaign。于是业务逻辑层就设计出 AccountService、OrderService、DeliveryService、CampaignService

  • 这种做法在项目简单是没什么问题,事实上项目简单时 你随便怎么设计都问题不大。

  • 但是当项目变大和复杂以后,就会出现问题了:

  • 组件臃肿:Service 组件的个数跟领域实体对象个数基本相当,必然造成个别 Service 组件变得非常臃肿——API 非常多,代码行数达到几千行;

  • 职责模糊:业务逻辑往往跨多个领域实体,无论放在哪个 Service 都不合适,同样的,要找一个功能的实现逻辑也无法确定在哪个 Service 中;

  • 代码重复 or 逻辑纠缠的两难选择:当遇到一个业务逻辑,其中的某个环节在另一个业务逻辑 API 中已经实现,这时如果不想忍受重复实现和代码,就只能去调用那个 API。但这样就造成了业务逻辑组件之间的耦合与依赖,这种耦合与依赖很快会扩散——新的 API 又会被其它业务逻辑依赖,最终形成蜘蛛网一样的复杂依赖甚至循环依赖;

  • 复用代码、减少重复虽然是好的,但是复杂耦合依赖的害处也很大——赶走一只狼引来了一只虎。两杯毒酒给你选!

前面截图的那个问题组件 ContractService 就是一个典型案例,这样的组件往往是热点代码以及整个项目的开发效率的瓶颈。

4.2 药方 1:倒金字塔结构——业务逻辑组件职责单一、禁止层内依赖


问题根源的反面其实就藏着解决方案,只是需要我们有意识的去改变习惯、遵循新的设计风格,而不是凭直觉去设计:

  • 业务逻辑层应该被设计成一个个功能非常单一的小组件,所谓小是指 API 数量少、代码行数少;

  • 由于职责单一因此必然组件数量多,每一个组件对应一个很具体的业务功能点(或者几个相近的);

  • 复用(调用、依赖)只应该发生在相邻的两层之间——上层调用下层的 API 来实现对下层功能的复用;

  • 于是系统架构就自然呈现出倒立的金字塔形状:越接近顶层的业务场景组件数量越多,越往下层的复用性高,于是组件数量越少。

4.3 症结 2:低内聚、高耦合


经典面向对象理论告诉我们,好的代码结构应该是“高内聚、低耦合”的:

  • 高内聚:组件本身应该尽可能的包含其所实现功能的所有重要信息和细节,以便让维护者无需跳转到其它多个地方去了解必要的知识。

  • 低耦合:组件之间的互相依赖和了解尽可能少,以便在一个组件需要改动时其它组件不受影响。

其实这两者就是一体两面,做到了高内聚基本也就做到了低耦合,相反如果内聚度很低,势必存在大量高耦合的组件。

我观察发现,很多项目都存在低内聚、高耦合的问题。根本原因在于很多程序员,甚至是很多经验丰富的程序员也缺少这方面的意识——对“内聚性”概念不甚清楚,对内聚性被破坏的危害没有意识,对如何避免更是无从谈起。

很多人从一开始就凭直觉写程序,有了一定经验以后一般能认识到重复代码的危害,对复用性有很强的认识,于是就会掉进一个陷阱——盲目追求复用,结果破坏了内聚性。

  • 业界关于“复用性”的认识存在一个误区——认为包括业务逻辑组件在内的任何层面的组件都应该追求最大限度的可复用性

  • 复用当然是好的,但那应该有个前提条件:不增加系统复杂度的情况下的复用,才是好的。

  • 什么样的复用会增加系统复杂性、是不好的呢?前面提到的,一个业务逻辑 API 被另一个业务逻辑 API 复用——就是不好的:

  • 损害了稳定性:因为业务逻辑本身是跟现实世界的业务挂钩的,而业务会发生变化;当你复用一个会发生变化的 API,相当于在沙子上建高楼——地基是松动的;

  • 增加了复杂性:这样的依赖还造成代码可读性降低——在一个本就复杂的业务逻辑代码中,包含了对另一个复杂业务逻辑的调用,复杂度会急剧增加,而且会不断泛滥和传递;

  • 内聚性被破坏:由于业务逻辑被打散在了多个组件的方法内,变得支离破碎,无法在一个地方看清整体逻辑脉络和实现步骤——内聚性被破坏,同时也意味着,这个调用链条上涉及的所有组件之间存在高耦合。

4.4 药方 2:复用的两种正确姿势——打造自己的 lib 和 framework


软件架构中有两种东西来实现复用——lib 和 framework,

  • lib 库是供你(应用程序)调用的,它帮你实现特定的能力(比如日志、数据库驱动、json 序列化、日期计算、http 请求)。

  • framework 框架是供你扩展的,它本身就是半个应用程序,定义好了组件划分和交互机制,你需要按照其规则扩展出特定的实现并绑定集成到其中,来完成一个应用程序。

  • lib 就是组合方式的复用,framework 则是继承式的复用,继承的 Java 关键字是 extends,所以本质上是扩展。

  • 过去有个说法:“组合优于继承,能用组合解决的问题尽量不要继承”。我不同意这个说法,这容易误导初学者以为组合优于继承,其实继承才是面向对象最强大的地方,当然任何东西都不能乱用。

  • 典型的继承乱用就是为了获得父类的某个 API 而去继承,继承一定是为了扩展,而不是为了直接获得一个能力,获得能力应该调用 lib,父类不应该去实现具体功能,那是 lib 该做的事。

  • 也不应该为了使用 lib 而去继承 lib 中的 Class。lib 就是用来被组合被调用的,framework 就是用来被继承、扩展的。

  • 再展开一下:lib 既可以是第三方的(log4j、httpclient、fastjson),也可是你自己工程的(比如你的持久层 Dao、你的 utils);

  • framework 同理,既可以是第三方的(springmvc、jpa、springsecurity),也可以是你项目内封装的面向具体业务领域的(比如 report、excel 导出、paging 或任何可复用的算法、流程)。

  • 从这个意义上说,一个项目中的代码其实只有 3 种:自定义的 lib class、自定义的 framework 相关 class、扩展第三方或自定义 framework 的组件 class。

  • 再扩展一下:相对于过去,现在我们已经有了足够多的第三方 lib 和 framework 来复用,来帮助项目节省大量代码,开发工作似乎变成了索然无味、没技术含量的 CRUD。但是对于业务非常复杂的项目,则需要有经验、有抽象思维、懂设计模式的人,去设计面向业务的 framework 和面向业务的 lib,只有这样才能交付可维护、可扩展、可复用的软件架构——高质量架构,帮助项目或产品取得成功。

4.5 症结 3:抽象不够、逻辑纠缠——High Level 业务逻辑和 Low Level 实现逻辑纠缠


当我们说“代码中包含的业务逻辑”的时候,我们到底在说什么?业界并没有一个标准,大家经常讲的 CRUD 增删改查其实属于更底层的数据访问逻辑。

我的观点是:所谓代码中的业务逻辑,是指这段代码所表现出的所有输入输出规则、算法和行为,通常可以分为以下 5 类:

  • 输入合法性校验;

  • 业务规则校验:典型的如检查交易记录状态、金额、时限、权限等,通常包含数据库或外部接口的查询作为参考;

  • 数据持久化行为:数据库、缓存、文件、日志等任何形式的数据写入行为;

  • 外部接口调用行为;

  • 输出/返回值准备。

当然具体到某一个组件实例,可能不会包括上述全部 5 类业务逻辑,但是也可能每一类业务逻辑存在多个。

单这样看你可能觉得并不是特别复杂,但是现实中上述 5 类业务逻辑中的每一个通常还包含着一到多个底层实现逻辑,如 CRUD 数据访问逻辑或第三方 API 的调用。

例如输入合法性校验,通常需要查询对应记录是否存在,外部接口调用前通常需要查询相关记录以获得调用接口需要的参数,调用接口后还需要根据结果更新相关记录状态。

显然这里存在两个 Level 的逻辑——High Level 的与业务需求对应且关联紧密的逻辑、Low Level 的实现逻辑。

如果对两个 Level 的逻辑不加以区分、混为一谈,代码质量立刻就会遭到严重损害:

  • 可读性变差:两个维度的复杂性——业务复杂性和底层实现的技术复杂性——被掺杂在了一起,复杂度 1+1>2 剧增,给其他人阅读代码增加很大负担;

  • 可维护性差:可维护性通常指排查和解决问题所需花费的代价高低,当两个 level 的逻辑纠缠在一起,会使排查问题变的更困难,修复问题时也更容易出错;

  • 可扩展性无从谈起:扩展性通常指为系统增加一个特性所需花费的代价高低,代价越高扩展性越差;与排查修复问题类似,逻辑纠缠显然也会使添加新特性变得困难、一不小心就破坏了已有功能。

下面这段代码就是一个典型案例——High Level 的逻辑流程(参数获取、反序列化、参数校验、缓存写入、数据库持久化、更新相关交易记录)完全淹没在了 Low Level 的实现逻辑(字符串比较、Json 反序列化、redis 操作、dao 操作以及前后各种琐碎的参数准备和返回值处理)。下一节我会针对这段问题代码给出重构方案。

@Override

4.6 药方 3:控制逻辑分离——业务模板 Pattern of NestedBusinessTemplate


解决“逻辑纠缠”最关键是要找到一种隔离机制,把两个 Level 的逻辑分开——控制逻辑分离,分离的好处很多:

  • 根据经验,当我们着手维护一段代码时,一定是想先弄清楚它的整体流程、算法和行为,而不是一上来就去研究它的细枝末节;

  • 控制逻辑分离后,只需要去看 High Level 部分就能了解到上述内容,阅读代码的负担大幅度降低,代码可读性显著增强;

  • 读懂代码是后续一切维护、重构工作的前提,而且一份代码被读的次数远远高于被修改的次数(高一个数量级),因此代码对人的可读性再怎么强调都不为过,可读性增强可以大幅度提高系统可维护性,也是重构的最主要目标。

  • 同时,根据我的经验,High Level 业务逻辑的变更往往比 Low Level 实现逻辑变更要来的频繁,毕竟前者跟业务直接对应。当然不同类型项目情况不一样,另外它们发生变更的时间点往往也不同;

  • 在这样的背景下,控制逻辑分离的好处就更明显了:每次维护、扩充系统功能只需改动一个 Levle 的代码,另一个 Level 不受影响或影响很小,这会大幅降低修改成本和风险。

我在总结过去多个项目中的教训和经验后,总结出了一项最佳实践或者说是设计模式——业务模板 Pattern of NestedBusinessTemplat,可以非常简单、有效的分离两类逻辑,先看代码:

public class XyzService {

@SuppressWarnings({ “unchecked”, “rawtypes” })

如果你熟悉经典的 GOF23 种设计模式,很容易发现上面的代码示例其实就是 Template Method 设计模式的运用,没什么新鲜的。

没错,我这个方案没有提出和创造任何新东西,我只是在实践中偶然发现 Template Method 设计模式真的非常适合解决广泛存在的逻辑纠缠问题,而且也发现很少有程序员能主动运用这个设计模式;一部分原因可能是意识到“逻辑纠缠”问题的人本就不多,同时熟悉这个设计模式并能自如运用的人也不算多,两者的交集自然就是少得可怜;不管是什么原因,结果就是这个问题广泛存在成了通病。

我看到一部分对代码质量有追求的程序员 他们的解决办法是通过"结构化编程"和“模块化编程”:

  • 把 Low Level 逻辑提取成 private function,被 High Level 代码所在的 function 直接调用;

  • 问题 1 硬连接不灵活:首先,这样虽然起到了一定的隔离效果,但是两个 level 之间是静态的硬关联,Low Level 无法被简单的替换,替换时还是需要修改和影响到 High Level 部分;

  • 问题 2 组件内可见性造成混乱:提取出来的 private function 在当前组件内是全局可见的——对其它无关的 High Level function 也是可见的,各个模块之间仍然存在逻辑纠缠。这在很多项目中的热点代码中很常见,问题也很突出:试想一个包含几十个 API 的组件,每个 API 的 function 存在一两个关联的 private function,那这个组件内部的混乱程度、维护难度是难以承受的。

  • 把 Low Level 逻辑抽取到新的组件中,供 High Level 代码所在的组件依赖和调用;更有经验的程序员可能会增加一层接口并且借助 Spring 依赖注入;

  • 问题 1 API 泛滥:提取出新的组件似乎避免了“结构化编程”的局限性,但是带来了新的问题——API 泛滥:因为组件之间调用只能走 public 方法,而这个 API 其实没有太多复用机会根本没必要做成 public 这种最高可见性。

  • 问题 2 同层组件依赖失控:组件和 API 泛滥后必然导致组件之间互相依赖成为常态,慢慢变得失控以后最终变成所有组件都依赖其它大部分组件,甚至出现循环依赖;比如那个拥有 130 个 import 和 40 个 Spring 依赖组件的 ContractService。

下面介绍一下 Template Method 设计模式的运用,简单归纳就是:

  • High Level逻辑封装在抽象父类AbsUpdateFromMQ的一个final function中,形成一个业务逻辑的模板;

  • final function保证了其中逻辑不会被子类有意或无意的篡改破坏,因此其中封装的一定是业务逻辑中那些相对固定不变的东西。至于那些可变的部分以及暂时不确定的部分,以abstract protected function形式预留扩展点;

  • 子类(一个匿名内部类)像“做填空题”一样,填充模板实现Low Level逻辑——实现那些protected function扩展点;由于扩展点在父类中是abstract的,因此编译器会提醒子类的程序员该扩展什么。

那么它是如何避免上面两个方案的 4 个局限性的:

  • Low Level 需要修改或替换时,只需从父类扩展出一个新的子类,父类全然不知无需任何改动;

  • 无论是父类还是子类,其中的 function 对外层的 XyzService 组件都是不可见的,即便是父类中的 public function 也不可见,因为只有持有类的实例对象才能访问到其中的 function;

  • 无论是父类还是子类,它们都是作为 XyzService 的内部类存在的,不会增加新的 java 类文件更不会增加大量无意义的 API(API 只有在被项目内复用或发布出去供外部使用才有意义,只有唯一的调用者的 API 是没有必要的);

  • 组件依赖失控的问题当然也就不存在了。

SpringFramework 等框架型的开源项目中,其实早已大量使用 Template Method 设计模式,这本该给我们这些应用开发程序员带来启发和示范,但是很可惜业界没有注意到和充分发挥它的价值。

NestedBusinessTemplat 模式就是对其充分和积极的应用,前面一节提到过的复用的两种正确姿势——打造自己的 lib 和 framework,其实 NestedBusinessTemplat 就是项目自身的 framework。

4.7 症结 4:无处不在的 if else 牛皮癣


无论你的编程启蒙语言是什么,最早学会的逻辑控制语句一定是 if else,但是不幸的是它在你开始真正的编程工作以后,会变成一个损害项目质量的坏习惯。

几乎所有的项目都存在 if else 泛滥的问题,但是却没有引起足够重视警惕,甚至被很多程序员认为是正常现象。

首先我来解释一下为什么 if else 这个看上去人畜无害的东西是有害的、是需要严格管控的

  • if else if …else 以及类似的 switch 控制语句,本质上是一种 hard coding 硬编码行为,如果你同意“magic number 魔法数字”是一种错误的编程习惯,那么同理,if else 也是错误的 hard coding 编程风格;

  • hard coding 的问题在于当需求发生改变时,需要到处去修改,很容易遗漏和出错;

  • 以一段代码为例来具体分析:

if (“3”.equals(object.getString(“type”))){

  • if (“3”.equals(object.getString(“type”)))

  • 显然这里的"3"是一个 magic number,没人知道 3 是什么含义,只能推测;

  • 但是仅仅将“3”重构成常量 ABC_XYZ 并不会改善多少,因为 if (ABC_XYZ.equals(object.getString(“type”))) 仍然是面向过程的编程风格,无法扩展;

  • 到处被引用的常量 ABC_XYZ 并没有比到处被 hard coding 的 magic number 好多少,只不过有了含义而已;

  • 把常量升级成 Enum 枚举类型呢,也没有好多少,当需要判断的类型增加了或判断的规则改变了,还是需要到处修改——Shotgun Surgery(霰弹式修改)

  • 并非所有的 if else 都有害,比如上面示例中的 if (list1 !=null) { 就是无害的,没有必要去消除,也没有消除它的可行性。判断是否有害的依据:

  • 如果 if 判断的变量状态只有两种可能性(比如 boolean、比如 null 判断)时,是无伤大雅的;

  • 反之,如果 if 判断的变量存在多种状态,而且将来可能会增加新的状态,那么这就是个问题;

  • switch 判断语句无疑是有害的,因为使用 switch 的地方往往存在很多种状态。

4.8 药方 4:充血枚举类型——Rich Enum Type


正如前面分析呈现的那样,对于代码中广泛存在的状态、类型 if 条件判断,仅仅把被比较的值重构成常量或 enum 枚举类型并没有太大改善——使用者仍然直接依赖具体的枚举值或常量,而不是依赖一个抽象。

于是解决方案就自然浮出水面了:在 enum 枚举类型基础上进一步抽象封装,得到一个所谓的“充血”的枚举类型,代码说话:

  • 实现多种系统通知机制,传统做法:

enum NOTIFY_TYPE { email,sms,wechat; } //先定义一个enum——一个只定义了值不包含任何行为的“贫血”的枚举类型

最后

那我们该怎么做才能做到年薪60万+呢,对于程序员来说,只有不断学习,不断提升自己的实力。我之前有篇文章提到过,感兴趣的可以看看,到底要学习哪些知识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

识才能达到年薪60万+。

通过职友集数据可以查看,以北京 Android 相关岗位为例,其中 【20k-30k】 薪酬的 Android 工程师,占到了整体从业者的 30.8%!

北京 Android 工程师「工资收入水平 」

[外链图片转存中…(img-l4fXU3pU-1715308794898)]

今天重点内容是怎么去学,怎么提高自己的技术。

1.合理安排时间

2.找对好的系统的学习资料

3.有老师带,可以随时解决问题

4.有明确的学习路线

当然图中有什么需要补充的或者是需要改善的,可以在评论区写下来,一起交流学习。

[外链图片转存中…(img-dUFp2ox8-1715308794898)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值