最近被朋友拉拢一起搞个小项目,我算是目前最后入伙的。
前几天提了个建议,把 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 1
为什么要把 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 2
以我有限的 java 经验来说,
静态方法缺点是不能黑魔法, 容器好像都是基于动态代理实现 AOP 之类的黑魔法, 反射估计也不能修改静态方法的行为, 所以我写 java 是尽量避免使用静态方法。 |
13
uselessVisitor 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
shenjinpeng 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 1
感觉无副作用的函数会比较适合写成静态的
|
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 1
@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); 这是鸭子的运动由自己控制,而不是画布控制。 就如现实世界: 交警根据司机是否违规开罚单,司机自己管开车。 每个类做好自己的事情情况下,尽量减少外部条件依赖。 外部依赖越少,代码越好维护。 |