Programming
组合优于继承(“favor composition over inheritance”)
前置资料:
组合灵活度高于继承:https://en.wikipedia.org/wiki/Composition_over_inheritance
设计模式(Gang of Four):https://en.wikipedia.org/wiki/Design_Patterns
组合:将抽象部分和实现部分分离,让他们都可以独立实现。
继承:抽象的同时实现,子类实现在父类实现的基础上。
具体实例
Js:class & 高阶函数(闭包加函数嵌套)
Rust:没得可以直接实现继承的语法 & trait
结论:
继承会导致强耦合。
领域建模中,若两个类都不具备对方的基础行为,那么这两个类间的继承关系是不恰当的(confusing the taxonomy)。这时候不可采用继承,而应该使用组合(如:js 高阶函数,rust trait)。
猜测:
因为继承代表了承接父类,能使用父类的方法,这很容易导致逻辑的走向呈现奇怪的环形(从子类的方法内部调用父类的方法)。最终导致逻辑的预测难度指数增加。
组合则是对某一行为的整体进行抽象,组合后的结构是一个 结构 + 行为1 + 行为2 … 的表现形式,是一个直观的平铺结构,更易理解。
继承对比组合
https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose
https://blog.gougousis.net/oop-inheritance-what-when-and-why/
对于何时使用继承或组合这里提到:
When you have a situation where either composition or inheritance will work, consider splitting the design discussion in two:
- The representation/implementation of your domain concepts is one dimension
- The semantics of your domain concepts and their relationship to one another is a second dimension
In general, inheriting within one of these dimensions is fine. The problem becomes when we forget to separate the two dimensions, and start inheriting across inter-dimensional boundaries.
这里提到了,域(domain),维(dimension),一维语义,用继承没有关系,二维语义内部维度的继承,则有问题(这里应该属于领域建模的内容,暂无了解,用几何来解释的话,二维的边缘是可确定的一维的(类似环形调用?),所以如果在二维内部进行连接,外部则产生混乱,这里的二维应该是指两个域(端点?)的关系)。
如这里的某个论证:
Finally, implementing a Stack by inheriting from ArrayList is a cross-domain relationship: ArrayList is a randomly-accessible Collection; Stack is a queuing concept, with specifically restricted (non-random) access8. These are different modeling domains. 用 ArrayList 实现栈是一个跨领域关系。
如何定义组合提到:
Without getting into the nuances of the difference between simple association, aggregation, and composition, let’s intuitively define composition as when the class uses another object to provide some or all of its functionality.
一个类通过一个对象来实现一些或全部它自己的方法(因为js的上下文为隐式,所以方法的概念显得很模糊不清,且无法静态检查)。
继承和组合的基本性
Inheritance is Fundamental Inheritance is fundamental to object-oriented programming. A programming language may have objects and messages, but without inheritance it is not object-oriented (merely “object-based”, but still polymorphic). …and so is Composition Composition is also fundamental to every language. Even if the language does not support composition (rare these days!), humans still think in terms of parts and components. It would be impossible to break down complex problems into modular solutions without composition. (Encapsulation is fundamental too, but we’re not going to talk about it much here.)
继承是面向对象的基础,没有了继承,面向对象应该叫基于对象,但仍旧能表现出多态(策略模式)。
组合是所有语言范式的基础。另外封装也属于所有范式的基础。
Semantics (interfaces) and mechanics (representation) could be separated, at the cost of additional language complexity;
这里的 semantics 和 mechanics 似乎属于领域建模用词
语义(接口:可以理解为定义)和 机制(表示:可以理解为真实行为,或具体实现)可以分开,但要付出额外的语言复杂性。
这里的复杂性,也就是类似 Rust 中的 struct 的使用,先抽象,struct + trait,再实现(impl + impl trait)。确实很麻烦,但保证了真实情况与想表达语义的完备。
ts 的抽象目前感觉还比较赢弱,interface 只能定义属性不能定义方法,抽象类又不能随意组合。
https://zhuanlan.zhihu.com/p/137707754
从这篇介绍抽象类组合的文章中也可以看到,为了阻止子错误访问父,还是只能通过报错来实现。
名词解释
semantics:一个对人和机器都有意义的 词 或 短语 集合。
Semantics in IT is a term for the ways that data and commands are presented.
Semantics is a linguistic concept separate from the concept of syntax, which is also often related to attributes of computer programming languages. The idea of semantics is that the linguistic representations or symbols support logical outcomes, as a set of words and phrases signify ideas to both humans and machines.
mechanics: 机制
继承的滥用
Domain classes should use implementation classes, not inherit from them. The implementation space should be invisible at the domain level. 域类,需要使用 实现 类,而不是继承他们。 在 领域级别,实现空 间应该不可见
也就是继承不可用来隐藏逻辑。
后一句跟产品经常喜欢讲的:我要这个效果,但具体怎么实现,有异曲同工之秒(笑),当然产品可以出了问题完全不管,开发是要管的。或者产品的:参考某处实现,这也是一种继承的滥用。
对于开发人员来说应该有这个思考:当我们在考虑软件的功能时,我们在 域级别 进行操作; 我们不希望被它如何做的细节所吸引。而恰恰继承是一个很大的噪音,如果实现后换个人来看,发现他 操作的类 继承了一个很复杂的父类,若出了问题,难免要去看看是不是继承了什么不该继承的东西。
The preferred (at least by me!) solution is to inherit from utility classes as much as necessary to implement your mechanical structures, then use these structures in domain classes via composition, not inheritance. Let me restate that: Unless you are creating an implementation class, you should not inherit from an implementation class. your application-domain classes should use implementation classes, not be one.
用 通用工具类 来实现机制结构,然后用这些 结构 在 域类 中通过组合来使用。
除非是在创建一个 实现类,不可继承一个实现类。
鸭子类型与trait
对于多态的限制:
鸭子类型是需要具名方法及具名属性(结构相同)匹配,则类型匹配;
trait则是需要具名结构(属性相同且结构体名相同)及具名接口;
很明显 trait 的限制更多,但在多态的可能性上却没有缺失。因为 trait 可以让某几个方法独立为一个接口(该接口用来描述一个行为整体),缺点是需要一定的抽象成本,但是静态检查更严格,且都是具名能够实现编译时优化。
可以看到,鸭子类型只要方法和属性对及可,但 trait 要求名称匹配,所以鸭子类型要对比类型相同需要先验证双方的所有方法和属性,这是必然的性能损耗。
Shell
nginx
网络请求 CROS报错,查看nginx日志
sentry 查看 nginx 容器中日志
// You can list all containers
sudo docker container ls
// then follow nginx logs
sudo docker container logs --follow sentry_onpremise_nginx_1
What does docker-compose ps
give you, does it say that the container is running or exited? Also, try docker-compose logs <container_id>
to see if something goes wrong with either Nginx or your actual application.
As far as I can tell, you’re starting your containers in daemon mode, so you don’t have access to the logs. I would recommend you perform the following steps to see if anything goes wrong while your application is restarting:
- Start the containers in daemon mode:
docker-compose up -d
- Attach to logs for both containers :
docker-compose logs
(in the directory where you havedocker-compose.yml
- Restart your application and see what the logs tell you.
错误处理
no route to host
网络请求 CROS 报错,response 可以看到 server 是 nginx,response 头都没有,说明请求都没能被处理,那肯定是后端 server 出了问题。
直接 nginx 日志查看报错
2021/04/25 10:21:36 [error] 7#7: *11 connect() failed (113: No route to host) while connecting to upstream, client: 192.168.72.175, server: , request: "POST /api/1/envelope/?sentry_key=367149017fee4e4c9ee1288d10ade1b1&sentry_version=7 HTTP/1.1", upstream: "http://172.22.0.29:3000/api/1/envelope/?sentry_key=367149017fee4e4c9ee1288d10ade1b1&sentry_version=7", host: "192.168.80.31:9000", referrer: "http://192.168.80.31:9000/"
2021/04/25 10:21:44 [warn] 7#7: *29 a client request body is buffered to a temporary file /var/cache/nginx/client_temp/0000000001, client: 192.168.72.175, server: , request: "POST /api/3/envelope/?sentry_key=e333c629ca434b80a7f7adf51349ed52&sentry_version=7 HTTP/1.1", host: "192.168.80.31:9000", referrer: "http://192.168.72.175:8002/"
2021/04/25 10:21:47 [error] 7#7: *29 connect() failed (113: No route to host) while connecting to upstream, client: 192.168.72.175, server: , request: "POST /api/3/envelope/?sentry_key=e333c629ca434b80a7f7adf51349ed52&sentry_version=7 HTTP/1.1", upstream: "http://172.22.0.29:3000/api/3/envelope/?sentry_key=e333c629ca434b80a7f7adf51349ed52&sentry_version=7", host: "192.168.80.31:9000", referrer: "http://192.168.72.175:8002/"
192.168.72.175 - - [25/Apr/2021:10:21:47 +0000] "POST /api/3/envelope/?sentry_key=e333c629ca434b80a7f7adf51349ed52&sentry_version=7 HTTP/1.1" 502 552 "http://192.168.72.175:8002/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36" "-"
根据 sentry 的架构图,请求进入 nginx 后会被统一转发给 relay,也就是这里的一个 docker 内部地址。
https://develop.sentry.dev/architecture/
既然是内部地址,应该不会不存在。但 nginx 日志报错 no route to host,说明要么是防火墙屏蔽(先把防火墙关了),要么是服务没起来,同时查看 docker-compose 各个容器状态:
docker-compose ps
当时是 retrying,应该意识到出问题了。
通过该回答进行处理,并重启,但未能生效
https://forum.sentry.io/t/how-to-configure-sentry-behind-firewall/11382/12
docker-compose down
docker-compose stop
docker-compose up -d
此时应该早点看日志:
docker container logs --follow sentry_onpremise_relay_1
// 或
docker-compose ps // 找到具体的容器名
docker-compose logs name
可以看到其实是配置文件出了问题,应该是安装过程中出现的错误,导致应该被删除的内容被遗留下来,json文件失效了。
所以还是应证了,早点看日志好。排查服务问题,应该先找到错误定位最近的模块的日志查看方式。