最近被朋友拉拢一起搞个小项目,我算是目前最后入伙的。
前几天提了个建议,把 redis 的工具类改成静态方法的形式(现在是以 spring bean 的形式交给容器管理),方便调用。结果另一位后端说不要,静态方法太多不好,问他为什么,说叫自己去看 JVM (黑人问号脸)。
我思考几天,也 google 了下,也没见着有特别有说服力的说法,自己勉强想到了一个点,那可能就是会导致 JVM 静态方法区容量过大,最终引起 OOM ?
但是,说实话,一个小项目,也就那点代码量,连一个工具类这点内存都要省了吗?
redis 连接使用的就是 Spring 托管的 RedisTemplate,从容器里获取到实例赋值给静态变量。
静态方法只是对 redisTemplate 的基础命令进行了一次简单的封装,当然,其实这个工具类的封装用依赖注入的方式或许是更好的选择,只是没有 [黑魔法] 的需求,所以选择了调用更方便的静态方法,仅此而已。
从设计模式、OO 的角度来看,static 不是一个好的选择。
![]() | 1 kop1989 2021-03-29 16:31:25 +08:00 完全没必要纠结。 我问你 1+1 等于几,你肯定会说等于 2 。而不会让我去用计算器。 因为你清楚 1+1 真的等于 2 。 同理。 |
2 Jooooooooo 2021-03-29 16:32:16 +08:00 不要过早 /过度优化. |
![]() | 3 NULL2020 OP @Jooooooooo 这段时间合作下来,我就觉得他有点这个问题,前面搞了两个月,注册登录权限都还没搞好,网上随便拿个轮子改一下,顶多一周搞定。 还有,一上来就整了套 spring cloud 微服务,汗。。 |
![]() | 4 opengps 2021-03-29 16:39:24 +08:00 via Android 现在的硬件资源很充沛,不用过早纠结 你大量静态会增加初始化时候的内存占用,但是实际上一个不合理的全表查询就已经吞掉了大量的内存,所以这个优化的必要性,其实根本不必要这么早 |
5 winnerczwx 2021-03-29 16:41:30 +08:00 假设他说的是对的 基于以上假设, 从程序员角度他是优秀的 从项目创始人角度, 他是不合格的 你们需要的是用最短时间做出 MVP 版本, 而不是纠结用不用静态类 |
6 securityCoding 2021-03-29 16:51:46 +08:00 via Android ![]() 为什么要把 redis 操作方法静态化 |
![]() | 7 kiripeng 2021-03-29 16:53:35 +08:00 只考虑技术相当于,经济学家不懂政治。。。。 |
8 quan01994 2021-03-29 16:55:59 +08:00 先把功能做出来,不要过早的优化。 |
![]() | 9 wolfie 2021-03-29 16:57:32 +08:00 为什么要把 redis 操作方法静态化 +1 |
![]() | 10 kaiki 2021-03-29 16:59:33 +08:00 先跑起来,手头没活就说在优化,以后顶不住就提桶跑路。 |
![]() | 11 qwerthhusn 2021-03-29 17:05:10 +08:00 RedisTemplate 不好使? Redisson ? Jedis ?都可以直接用啊 |
![]() | 12 zjsxwc 2021-03-29 17:07:54 +08:00 ![]() 以我有限的 java 经验来说, 静态方法缺点是不能黑魔法, 容器好像都是基于动态代理实现 AOP 之类的黑魔法, 反射估计也不能修改静态方法的行为, 所以我写 java 是尽量避免使用静态方法。 |
13 zm8m93Q1e5otOC69 2021-03-29 17:10:53 +08:00 为什么要静态化?托管不是很好? |
![]() | 14 arthas2234 2021-03-29 17:10:58 +08:00 |
15 GM 2021-03-29 17:17:39 +08:00 public static 方法 == 全局函数 public static 变量 == 全局变量 看到这种代码我一般都呸一下,我就 tmd 想调用你一个方法,你 tmd 自己把整个系统都串起来跑。 这种代码想做单元测试?做梦比较合适。 |
![]() | 16 ouyc 2021-03-29 17:22:39 +08:00 就算省能省到哪里去,几个静态方法难道还能撑破 JVM 。麻烦你问下那位后端,我想知道为什么静态方法多了不好,竟然还是关于 JVM 的 |
![]() | 17 NULL2020 OP @securityCoding @wolfie @qwerthhusn bean 的方式是在每个使用的地方引入,并且在业务代码处一堆重复的代码: redisTemplate.opsForXXX() 而静态方法的方便之处就是不需要引入,把 redisTemplate.opsForXXX() 这些重复代码都封装在一个方法里了 @beichenhpy 各有各的好处吧,redis 操作本来就是一些基本命令操作,类似于工具方法,所以习惯使用静态方法 |
![]() | 18 NULL2020 OP |
![]() | 20 pushback 2021-03-29 17:29:46 +08:00 先做出来,再优化,起步太高(指考虑太多)在小项目里只会拖延进度 |
![]() | 21 urlk 2021-03-29 17:30:23 +08:00 基于面向对象还是函数式编程 ? 如果以后要对缓存 /db driver 进行封装, 需要把 redis 也封装进去, 那之前写的不就废了 . |
22 cccssss 2021-03-29 17:33:48 +08:00 静态方法怎么连的 redis ?静态工具类里写死的么?连接池咋解决的? |
![]() | 23 chendy 2021-03-29 17:35:02 +08:00 问题是为什么不用 spring 的 bean 的方式? 静态不方便加黑魔法,比如切面,比如 mock,等等等等 |
![]() | 24 tabris17 2021-03-29 17:35:43 +08:00 不至于影响 JVM 效率,不过耦合度太高了,不符合面向接口编程的模式 |
![]() | 25 NULL2020 OP |
27 cccssss 2021-03-29 17:54:56 +08:00 |
![]() | 28 agdhole 2021-03-29 18:20:43 +08:00 额外造个 facade |
30 hefish 2021-03-29 18:38:09 +08:00 能一上来搞 springcloud 的,肯定是大神。我这儿一个活动管理系统,也被做成了分布式的,zookeeper 都用上了。 可能工程师是大户人家出身,出手阔绰。 |
31 dqzcwxb 2021-03-29 18:38:31 +08:00 楼主说的是静态获取 redis 对象,而不是静态创建 redis 链接 简单讲就是把 @Resource 注入变成 RedisaMange.inst()方式获取 redis 操作对象 二者实际上没有本质区别,都是单例只是获取方式的写法不同 你同事说的含糊其辞估计他也不懂只是不想改,你想改的理由也不充分 所以,不改 |
32 Cbdy 2021-03-29 18:40:42 +08:00 via Android LZ 同事说得没错,去看看 JDK 源码 |
33 munan56 2021-03-29 19:29:34 +08:00 他只是不想改。 |
34 GM 2021-03-29 19:58:45 +08:00 @NULL2020 我就问一句:静态函数你怎么注入?如果你们没用依赖注入的话,当我没说过。 为什么我这么恶心静态函数调用,是因为我维护过一个系统,里面每个 Service 都是静态函数互相调用,任何一个函数调用,都会调用到 B 、C 、D...模块里面的多个静态函数,然后这 B 、C 、D...模块里面的多个静态函数里面,又分别调用 EFG... 里面的 N 个静态函数,总之,一句话,想调用任何一个函数,必须要会启动整个系统,才能成功调用。想简单 mock 一下来测试一个某个函数?不可能,因为全是静态函数调用。 |
![]() | 39 huijiewei 2021-03-29 21:47:07 +08:00 业务写静态是有多想不开 顶多工具类用用静态 |
40 kassadin 2021-03-29 22:51:22 +08:00 写一回测试就差不多 get 到了 |
![]() | 41 24bit 2021-03-29 23:06:26 +08:00 ![]() 感觉无副作用的函数会比较适合写成静态的 |
![]() | 42 24bit 2021-03-29 23:24:59 +08:00 Redis 工具类感觉大概率会依赖外部配置,同时对 Redis 进行修改,写成静态类本质上也就是利用静态全局变量来托管 Redis 对象。 这和用容器来托管感觉没有太多本质的区别,而且使用容器和可以享受 Spring 带来的很多便利。 对于 JVM,静态类感觉其实也就是方法区会多点静态常量?而且静态方法可以通过 invokestatic 调用,这个在性能上应该会比 invokevirtual 好一些。 |
44 gadsavesme 2021-03-30 00:03:17 +08:00 一般上来问题说不清楚,动不动就是自己去看 xxx 的人,大概率自己就是个半吊子。方法区 oom 的我见过,写太多静态方法导致 oom 的我是孤陋寡闻了,几十 m 的内存就够你复制静态方法复制到手抽筋了。 |
45 GM 2021-03-30 10:11:47 +08:00 ![]() @NULL2020 我是拿一个极端的案例来说明为什么不要用 public static,你这个使用场景明显就属于不适合使用的场景。 什么场景适合使用呢?完全无状态的、不依赖、不修改任何外部状态的函数可以使用 public static 。 |
46 GM 2021-03-30 10:12:49 +08:00 @gadsavesme JVM8 以上不存在方法区 OOM 问题。 |
![]() | 47 wolfie 2021-03-30 10:56:17 +08:00 |
![]() | 49 q149072205 2021-03-30 11:19:12 +08:00 static 速度快啊,不用实例化啊。。 |
50 CODEWEA 2021-03-30 11:31:14 +08:00 因为你的提出的意见毫无价值,没有修改的必要性 |
51 hantsy 2021-03-30 11:33:09 +08:00 》那可能就是会导致 JVM 静态方法区容量过大,最终引起 OOM ? 这个是存在的。 工具类使用 static,要看情况。spring core 中有一个 org.springframework.utils 都是工具类,没必要实例化,用容器管理。 |
![]() | 52 encro 2021-03-30 13:01:58 +08:00 赞同 @GM 修改内部属性和数据的通常都不用静态。 可能连接池后者单例的都不用静态。 案例一: 比如 redis 操作,没必要静态。 r = new redis(); r->set(); 因为这里 redis 可能是连接池,也可能是单例,这时候为了维护容易,不要再去做 redis::set 方法,因为静态方法可能跳过构造函数。 假设有多个 redis 数据库,那么可以是 r1 = new redis(db1); r2 = new redis(db2); 静态方法可能就成了 redis::set(key,val,db1) 用起来就纠结了 案例二: mvc 的 model 里面通常除了 create 方法,都不要静态(静态常量可以有) Class Product{ const STATE_DRAFT =0 const STATE_PUBLISH =1 public static function create(data){} public function update(data){} public function remove(){} } 这时候 update,remove 不静态,是为了减少对外暴露接口,方便代码统一修改。 create 用静态是因为它返回了 product 实例 |
![]() | 54 no1xsyzy 2021-03-30 13:42:19 +08:00 神说:过早或过度优化是万恶之源 过早或过度抽象也差不了多少。 前期能不封装就不封装,能 Ctrl-C Ctrl-V 决不 Extract function / method 不过,如果你学过 Haskell 的话你会很清楚如何抽象,这个语言是抽象适度程度的训练。 简单地说,面向对象的核心是印欧语系的主谓结构,class 是本体论的映射,interface 是认知论的映射。 @winnerczwx 你需要注意一下一个背景条件:当前已经完成了一个实现。因此从项目创始人角度来说,不改才是对的。 @GM 静态函数完全可以做出类似依赖注入的效果,把需要注入的部分作为参数显式传递。这大约就是 Functor ? C 时代就是这么写的,Go 的方法书写起来也差不多这个感觉,至于 Python 的话 self 都是显式传递的…… |
![]() | 55 no1xsyzy 2021-03-30 13:53:54 +08:00 @no1xsyzy class 是范畴论的映射,object 是本体论的映射,interface 和 trait 是认知论的两种学说的映射(虽然 Java 的 interface 其实是 trait ?),前者我知道是 “鸭子定律” 的映射,后者不清楚有什么称呼。 提问:你的 redis 操作是范畴论下的操作,还是本体论下的操作? 存在一个相关范畴,你在该范畴下描述一个客观规律?还是说,这是存在一个东西,你在描述一个东西的行为? |
![]() | 56 HolmLoh 2021-03-30 14:46:27 +08:00 如果我没记错的话 java8 已经用元空间替代掉了原来的永久代 元空间是用的直接内存,只要你的机器够,就不会 OOM 所以因为内存原因而不能用静态方法是没有道理的 |
57 1109599636 2021-03-30 14:56:04 +08:00 我一个写 go 和 py 的也想知道为什么,但是 50 多楼下来没有人能给出一个有说服力的答案。。。 |
59 GM 2021-03-30 15:17:38 +08:00 @1109599636 我在 15 楼已经给出了。 |
![]() | 61 FreeEx 2021-03-30 15:31:27 +08:00 问题不在于静态方法,而在于上了 spring 的船为什么不用 RedisTemplate ? |
![]() | 62 NULL2020 OP |
![]() | 64 Marszm 2021-03-30 15:46:08 +08:00 幸好,我们这边就我懂 redis..我想怎么用怎么用.. |
65 namelosw 2021-03-30 16:10:47 +08:00 把 redis 的工具类改成静态方法的形式(现在是以 spring bean 的形式交给容器管理),方便调用 > 我怀疑你们很少写测试 |
66 namelosw 2021-03-30 16:16:41 +08:00 @no1xsyzy 其实 Haskell 也用依赖注入,只不过是走 Reader Monad 或者 Tagless Final 的形式。 他这个问题本质上就是回答系统会有几个 Redis (比如有假的 Redis 用来测试,或者用来做六边形架构,下面可能会替换多套实现),如果大于 1,就走依赖注入,如果等于 1 就无所谓。 |
![]() | 67 chocotan 2021-03-30 16:18:13 +08:00 封不封装跟静态方法一点关系都没有 |
![]() | 68 est 2021-03-30 16:21:04 +08:00 redis 还是建议不要搞成静态方法。因为这玩意一般还要挂一个连接池。 |
![]() | 69 encro 2021-03-30 18:39:19 +08:00 通常来说就是调用静态方法创建实例,然后调用实例的方法来修改、摧毁自身。 选择这样: canvas= new canvas(); duck=new duck(); duck.setColor(red); duck.run(speed); duck.eat(); duck.die(); cavas.add(duck); 而不是这样: canvas= new canvas(); duck=new duck(); addDuckToCanvas(duck,canvas); 更不要: moveDuck(canvas,duck,speed) 为什么?因为实际情况可能是: canvas= new canvas(); duck=new duck(color,speed,size,direction); canvas.add(duck); 这是鸭子的运动由自己控制,而不是画布控制。 就如现实世界: 交警根据司机是否违规开罚单,司机自己管开车。 每个类做好自己的事情情况下,尽量减少外部条件依赖。 外部依赖越少,代码越好维护。 |