> 2021年11月13日信息消化 ### Workspaces | 103 - Julius Tarng origin: [103 - Julius Tarng](https://www.workspaces.xyz/p/103-julius-tarng?) #### Desk - **Natural light** — I am a plant, if I don't get sunlight I can't live~ - **Notebooks**, Midori MD & Maruman Mnemosyne — I studied Industrial Design, and I am still gerbil-trained to draw cubes and cylinders automatically, on paper. - Pens, Rays Gel Ink & Pilot Acroball — I bulk order pens imported from Taiwan and Japan (plug: my favorite stationery store in the US — [Yoseka Stationery](https://yosekastationery.com/)) just to try them out. These two are my current favorites! - **Tobii Eye Tracker 4** (not shown) — Earlier this year I started [coding by voice](https://whalequench.club/), learning [Talon Voice](https://talonvoice.com/) and using an eye tracker to control the mouse. It magnetically mounts to the bottom of my monitor. #### Software - [**Time Out**](https://www.dejal.com/timeout/) — forces me to take breaks, customized to 10s break every 10m, and 2m break every hour, often in the middle of important screen presentations. At this point everyone I work with has seen it once and appreciates the mandatory stretch break. - [Fish Shell](https://fishshell.com/) — I was in search for a friendly shell that didn't require me reading deep stackoverflow posts to customize, and Fish worked great in the default Terminal app. - Codesandbox — I do a lot of prototyping in my work, and CSB has been such a nice way to spin up small experiments. I have my eye on Stackblitz but their Typescript support is still lacking. - [read.cv](https://read.cv/tarngerine) — LinkedIn is dead, long live [read.cv](http://read.cv/)! ### Daily Coding Problem: Problem #256 [Medium] Given a linked list, rearrange the node values such that they appear in alternating `low -> high -> low -> high ...` form. For example, given `1 -> 2 -> 3 -> 4 -> 5`, you should return `1 -> 3 -> 2 -> 5 -> 4`. ```python #!/usr/bin/env python3 # -*- coding: utf-8 -*- class LinkedList: def __init__(self): self.head = None # This method goes through all the nodes of the linked list. It yields each node and moves to the next node until it encounters a node that is None (end of the list). def __iter__(self): node = self.head while node is not None: yield node node = node.next def visualize(self): node = self.head nodes = [] while node is not None: nodes.append(node.data) node=node.next nodes.append("None") print(" -> ".join(map(str, nodes))) def insert_last(self, node): if self.head is None: self.head = node return for current_node in self: pass current_node.next = node class Node: def __init__(self, data): self.data = data self.next = None mylist = LinkedList() testnum=[2,1,3,4,10,5] for i in testnum: mylist.insert_last(Node(i)) mylist.visualize() def rearrange_list(o_list): ``` ### Practical SOLID in Golang: Open/Closed Principle > MEMO > > Open/Closed Principle 是第二个 SOLID 原则,它代表字母 O。它表示我们应该始终扩展我们的代码结构而不实际修改它们。 在它的源头上,我们应该使用多态来满足这个要求。我们的代码应该提供一个简单的接口来添加这种可扩展性。 > > The Open/Closed Principle says that software structures should be **open for extension** but **closed for modification**. origin: [Practical SOLID in Golang: Open/Closed Principle](https://levelup.gitconnected.com/practical-solid-in-golang-open-closed-principle-1dd361565452) My opinion is that is the case with The Open/Closed Principle, which stands for the letter O in the word SOLID. In my experience, only people eager to learn about SOLID genuinely understand what this principle means. We had a chance to learn some applications of this principle in the Strategy pattern without realizing we applied it. Still, Strategy pattern is just one application of OCP. > You should be able to extend the behavior of a system without having to modify that system. > 您应该能够扩展系统的行为而无需修改该系统。 The requirement for OCP, which we can see above, Uncle Bob provided in his blog. I prefer this way of defining The Open/Closed Principle as it shows its full brilliancy. After the first look, we may see it as an absurd requirement. Yes, seriously, how should we extend something without modifying it? I mean, is it possible to change something without changing it? By checking the code example below, we can see what it means for some structures not to respect this principle and possible consequences. 乍一看,我们可能会认为这是一个荒谬的要求。是的,说真的,我们应该如何在不修改的情况下扩展某些东西?我的意思是,有没有可能改变某些东西而不改变它? 通过检查下面的代码示例,我们可以看到某些结构不遵守此原则和可能的后果的含义。 ```go import ( "net/http" "github.com/ahmetb/go-linq" "github.com/gin-gonic/gin" ) type PermissionChecker struct { // // some fields // } func (c *PermissionChecker) HasPermission(ctx *gin.Context, name string) bool { var permissions []string switch ctx.GetString("authType") { case "jwt": permissions = c.extractPermissionsFromJwt(ctx.Request.Header) case "basic": permissions = c.getPermissionsForBasicAuth(ctx.Request.Header) case "applicationKey": permissions = c.getPermissionsForApplicationKey(ctx.Query("applicationKey")) } var result []string linq.From(permissions). Where(func(permission interface{}) bool { return permission.(string) == name }).ToSlice(&result) return len(result) > 0 } func (c *PermissionChecker) getPermissionsForApplicationKey(key string) []string { var result []string // // extract JWT from the request header // return result } func (c *PermissionChecker) getPermissionsForBasicAuth(h http.Header) []string { var result []string // // extract JWT from the request header // return result } func (c *PermissionChecker) extractPermissionsFromJwt(h http.Header) []string { var result []string // // extract JWT from the request header // return result } ``` The example shows one struct, `PermissionChecker`. It should check if there is the required permission to access some resource, depending on the Context of the web application, supported by the package Gin. Here we have the primary method, `HasPermission`, which checks if permission with specific names is associated with the data within the `Context`. The retrieval of permissions from Context may vary depending on whether the user authorizes with a JWT token, basic authorization, or application key. Inside the struct, we provided all different extractions of the permission slice. If we respect [The Single Responsibility Principle](https://levelup.gitconnected.com/practical-solid-in-golang-single-responsibility-principle-20afb8643483), `PermissionChecker` is responsible for deciding if permission is inside the `Context`, and it does not have anything with the authorization process. It must be the case that the authorization process is defined somewhere else, in some other struct, maybe even a different module. So, if we want to extend the authorization process somewhere else, we need also to adapt logic here. Suppose we want to extend the authorization logic and add some new flow, such as keeping user data in session or using [Digest Authorization](https://httpwg.org/specs/rfc7616.html). In that case, we need to make adaptations in `PermissionChecker` as well. #### How we do respect The Open/Closed Principle > The Open/Closed Principle says that software structures should be open for extension but closed for modification. The statement from above gives some possible directions for our new code that should respect OCP. That piece of code should provide something that would allow extension pushed from the outside. In Object-Oriented Programming, we support such extensions by using different implementations for the same interface. In other words, we use [polymorphism](https://en.wikipedia.org/wiki/Polymorphism_(computer_science)). ```go type PermissionProvider interface { Type() string GetPermissions(ctx *gin.Context) []string } type PermissionChecker struct { providers []PermissionProvider // // some fields // } func (c *PermissionChecker) HasPermission(ctx *gin.Context, name string) bool { var permissions []string for _, provider := range c.providers { if ctx.GetString("authType") != provider.Type() { continue } permissions = provider.GetPermissions(ctx) break } var result []string linq.From(permissions). Where(func(permission interface{}) bool { return permission.(string) == name }).ToSlice(&result) return len(result) > 0 } ``` In the example above, we can see one candidate that respects OCP. The adapter `PermissionChecker` does not hide technical details about extracting permissions from the `Context`. 在上面的例子中,我们可以看到一位尊重 OCP 的候选人。适配器 `PermissionChecker` 不会隐藏有关从 `Context` 中提取权限的技术细节。 Instead, we introduced a new interface, `PermissionProvider`. This new construct represents the place for placing the logic for different permission extraction. 相反,我们引入了一个新接口 PermissionProvider。这个新结构代表放置不同权限提取逻辑的位置。 For example, it can be `JwtPermissionProvider`, or `ApiKeyPermissionProvider`, or `AuthBasicPermissionProvider`. Now, the module responsible for authorization may also contain extractors for permissions. 例如,它可以是`JwtPermissionProvider`、`ApiKeyPermissionProvider`” 或`AuthBasicPermissionProvider`。现在,负责授权的模块也可能包含权限提取器。 That means we can keep the logic about authorized users inside one place instead of spreading it all over the code. 这意味着我们可以将有关授权用户的逻辑保留在一个地方,而不是将其散布在整个代码中。 On the other hand, our primary goal, to extend `PermissionChecker`, without a need to modify it, now is possible. We may initialize `PermissionChecker` with as many different `PermissionProviders` as we want. Suppose we need to add a possibility to get permissions from the session key. In that case, we need to implement a new `SessionPermissionProvider`, that will extract the cookie from the Context and use it to fetch permissions from the `SessionStore`. We made it possible to extend `PermissionChecker` whenever we need it, without modifying its internal logic anymore. Now we see what it means to be open for extension and closed for modification. #### Some more examples The previous problem we may solve with a slightly different approach. Let us look at the following code snippet: ```go type PermissionProvider interface { Type() string GetPermissions(ctx *gin.Context) []string } type PermissionChecker struct { // // some fields // } func (c *PermissionChecker) HasPermission(ctx *gin.Context, provider PermissionProvider, name string) bool { permissions := provider.GetPermissions(ctx) var result []string linq.From(permissions). Where(func(permission interface{}) bool { return permission.(string) == name }).ToSlice(&result) return len(result) > 0 } ``` With the new implementation, we removed the internal slice of `PermissionProviders` from `PermissionChecker`. Instead, we define the right provider as an argument for the method `HasPermission`. I like more the first approach, but this one also may be a solution, depending on the use case we have in our application. We can apply The Open/Closed Principle to methods, not just on structs. The example may be the code from below: ```go func GetCities(sourceType string, source string) ([]City, error) { var data []byte var err error if sourceType == "file" { data, err = ioutil.ReadFile(source) if err != nil { return nil, err } } else if sourceType == "link" { resp, err := http.Get(source) if err != nil { return nil, err } data, err = ioutil.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() } var cities []City err = yaml.Unmarshal(data, &cities) if err != nil { return nil, err } return cities, nil } ``` The function `GetCities` reads the list of cities from some source. That source may be a file or some resource on the Internet. Still, we may want to read data from memory, from Redis, or any other source in the future. So somehow, it would be better to make the process of reading raw data a little more abstract. With that said, we may provide a reading strategy from the outside as the method argument. ```go type DataReader func(source string) ([]byte, error) func ReadFromFile(fileName string) ([]byte, error) { data, err := ioutil.ReadFile(fileName) if err != nil { return nil, err } return data, nil } func ReadFromLink(link string) ([]byte, error) { resp, err := http.Get(link) if err != nil { return nil, err } data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } defer resp.Body.Close() return data, nil } func GetCities(reader DataReader, source string) ([]City, error) { data, err := reader(source) if err != nil { return nil, err } var cities []City err = yaml.Unmarshal(data, &cities) if err != nil { return nil, err } return cities, nil } ``` As you can see in the solution from above, in Go, we may define a new type that embeds the function. Here we described a new type, `DataReader`, representing a function type for reading raw data from some source. New methods `ReadFromFile` and `ReadFromLink` are actual implementations of the DataReader type. The `GetCities` method expects the actual implementation of `DataReader` as an argument, which then executes inside the function body and takes raw data. As you can see, the primary purpose of OCP is to give us more flexibility in our code and to the users of our code. Our libraries have real value as soon as someone can extend our libraries without forking them, providing pull requests for them, or modifying them in any way. #### Conclusion The Open/Closed Principle is the second SOLID principle, and it stands for the letter O. It says that we should always extend our code structures without actually modifying them. In its source, we should use polymorphism to fulfill this requirement. Our code should provide a simple interface to add such extendability. Open/Closed Principle 是第二个 SOLID 原则,它代表字母 O。它表示我们应该始终扩展我们的代码结构而不实际修改它们。 在它的源头上,我们应该使用多态来满足这个要求。我们的代码应该提供一个简单的接口来添加这种可扩展性。 ### 写了 20 期 Newsletter,我有这些想和你分享 origin: [写了 20 期 Newsletter,我有这些想和你分享](https://sspai.com/post/69882) YouTuber Ali Abdaal 在 一个[播客](https://nathanbarry.com/048-ali-abdaal-building-multiple-income-streams-content-creator/) 里说,「**YouTube 可以改变你的人生,但你需要在接下来的两年中每周发布一个视频。如果你这样做,我保证它会改变你的人生**」。我相信写作也是一个道理。 五个月离两年还差很远,但作为一个在线写作新手,我在这几个月的摸索中有一些心得体会,希望分享出来帮助考虑开始但还没有行动的朋友。如果你已经在写了,我希望能借此和你交流学习。 文章比较长,主要分为三个部分。第一部分着重聊一聊写作,解释一下前面提到的「塑造新的自我」,同时回答「写什么」和「怎么写」的问题。第二部分是一些具体的经验、感想和建议,最后是感恩和推荐一些链接。 #### 在写作中认识自己 「Writing is an education in editing out the ego.」这句话是在 Steven Pressfield 的 Nobody Wants to Read Your Sh*t 一书读到的,整个句子很长却耐人寻味: > If it’s our soul that we’re talking about (rather than just What We Write), then our passage through the varying disciplines of this life, if we’re truly paying attention, is an education in editing out the ego, in stepping away from our fear and self-concern and aspirations for recognition, for material rewards, and for earthly payoffs, until we move into the realm of the gift, where what we offer is for the reader’s good and not our own. ##### 为自己而写 很幸运的是刚开始的时候就读到这个「为自己而写」的建议。这听起来和上面那段引用里的「为读者的好」(for the reader's good) 相矛盾,我试着理解一下。 为自己而写的第一层理解是「写自己感兴趣的」。有句话说「只有照顾好自己,才能照顾好别人」。只有写自己喜欢的内容,才能保持热情,把这件事持续做下去。 第二层理解是「写自己想读的」。虽然我们写的最终要给别人读,但这么想的话操作起来很难。我们自己的品味是一杆很好的标尺。Morgan Housel 是我很喜欢的作者,他说他评价自己写得好不好的方法是问自己是否喜欢这句话或者这篇文章。另一个类似的说法是「写你的朋友想读的」。Tim Ferris 被问到 为什么他十年前出版的 The 4-Hour Work Week 到现在还是亚马逊上引用最多的书,他说如果要给出一个解释的话,他觉得是因为他当时是特意为两个朋友而写的。内容和语气都非常个人化,很多人读起来就像专门写给自己的,很有共鸣。这层理解用 David Perell 的话总结就是要 为某个具体的人而写。 第三层理解是「为大我而写」。Write for yourself 也可以说成 write for your self,而不是 write for your ego。Self 和 Ego 用中文或许可以翻译为「大我」和「小我」。「小我」从内看外,而「大我」由外看内。 举个例子就很好理解了。常年记日记的朋友可能都有这样的体验,翻看以前写的,状态好的时候大多在谈事情、谈理想,而状态不好的时候都是在说情绪,碎碎念。日记都是为自己而写的,但连我自己都不想再去读以前那些充满负能量的碎碎念。如果说「大我」太抽象,那也可以说是「为未来的我而写」。Steven Pressfield 解释书名 Nobody Wants to Read Your Sh*t 是什么意思,他说没有人想读你满是以自我为中心的内容: > None of us wants to hear your self-centered, ego-driven, unrefined demands for attention. Why should we? It’s boring. There’s nothing in it for us. #### 写掉自己的 ego 如果说前两层理解是可以直接应用的技巧,那第三层就是需要用一生修炼的功夫。 有时候写作中的 ego 不是那么好觉察到的。我一开始为了把自己的想法凑成一篇文章,会刻意去找一些科学文献来支撑我的观点。我写得不流畅,估计读者读起来也不流畅。后来我意识到,我的引用更像是为了证明自己是对的(你看别人也这么说),**但引用应该是为了深入观点。一个为 ego 服务,另一个为内容服务**。 经常读我内容的朋友可能会发现,我常常引用相同的作者和书籍,有时候一本书的内容我要写好多期。心里总是有一些声音:别人会不会以为我只读过这些书,他们会不会觉得我也就这个水平。我可以给出很好的解释,引用斯多葛哲学家 Seneca 的建议,「反复阅读少数的有智慧的作者」,来让自己也显得很有智慧。但如果我忽略 ego 的声音,事实上,那些书都是我最近一年读的。以前读书少,读了也不记得。写作让我读了更多的书,也内化了更多的知识。我只是一个初学者,「stay foolish」。 不过,当订阅数变多了谁都有这样的压力。Ali Abdaal 在最近一期 newsletter 正好也聊到了 Fear of Irrelevancy,他也担心有一天大家对他的视频不感兴趣了。Shane Parrish 说到 Impostor Syndrome 可以是一件好事,它激励我们进步。Steven Pressfield 在 The War of Art 有句话说,「如果你发现你问自己『我真的是一个作家吗?我真的是一个艺术家吗?』,那你很可能就是了」。 #### 写出自己的风格 文字风格的变化很难描述,但可以用我 newsletter 的形式变化来理解。我一开始也不知道以什么形式写。Atomic Habits 的作者 James Clear 在写书之前就是每周发布两篇高质量的文章,我很受鼓舞,我想我利用业余时间应该可以一周写一篇吧。又看到很多人会推荐一些链接,于是决定采用文章加推荐链接的形式。可是后来发现当我把积累了一段时间的内容写完之后,我没有那么多东西写了。而为了推荐链接我有时要刻意地去读文章,但我希望自己主要以书本为信息来源。写着写着我发现自己总是有几个固定关注的话题,每周会有一些自己的体会。 写作就像一场认识自己、创造新自我的旅程。Austin Kleon 在 Show Your Work 写道,「不要把你的网站看成是一个自我宣传的机器,把它看成一个自我发明的机器。」James Clear 说,「你所采取的每一个行动都是对你希望成为的人的投票」。别停下来。 #### 关于增长订阅量 我个人认为 newsletter 相对于博客没有从本质上提高传播性。唯一的优势就是可以被收录在中文 newsletter 的列表里(后面会给推荐),但随着列表越来越长,自己的很容易被淹没。不过算是搭上了一波浪潮(在国内可能还只是一点浪花),说「订阅RSS」比「订阅 newsletter」要更吸引人。 想要做宣传还是要通过其它渠道,比如通过社交媒体,或者将内容发布到其他(使用算法推荐的)平台。对于已经长期在社交平台上分享想法的朋友来说要容易很多,我以前除了微信朋友圈不用别的,也不知道在国内除了微博大家还用什么平台。对我来说一开始还挺难的。有几点经验想说一说。 #### 清楚你想要的数量和质量 尽量避免两个极端。一个是完全不宣传自己的作品,希望等到自己写得足够好然后被人发现。Austin Kleon 在 Show Your Work 说,「作品好本身是不够的,如果要被别人发现,你要给别人发现你的机会。」(**It’s not enough to be good. In order to be found, you have to be findable.**) > 关键还是继续写 如果你已经有一定的群众基础,或者有相关的行业经验,你更容易获得别人的信任。但像我这样的无名作者,还是得用作品说话。我写到第 10 期都只有十几个订阅者,后来有幸得到了一位创作者的推荐,一下就破了一百。就像投资一样,要有耐心,频繁的操作而忽略把时间花在更有价值上的事情上只会帮倒忙,还不如 shut up and wait。 #### 资源推荐 以下几个链接列了更多的发布工具和中文 newsletter: - [播客简报导航](https://podletter.club/) - [Newsletter-list (Github)](https://github.com/chasays/newsletter-list) ##### 关于写作 Daniel Gilbert 的 "Rules for Good Writing" On Writing Well by William Zinsser - Goodreads, 豆瓣:一本很经典的关于非虚构写作的书,被纽约时报评价为写作圣经 Nobody Wants to Read Your Sh*t by Steven Pressfield - Goodreads, 豆瓣:被 Morgan Housel 列为改变他人生的 23 本书之一,是对他写作影响最大的一本书 ##### 关于营销 Show Your Work! by Austin Kleon - Goodreads, 豆瓣:在构思这篇文章的时候读的,我希望我刚开始就读到这本书(别因为不喜欢中文版的名字而跳过它) How to Crush it on Twitter: David Perell and Matthew Kobach Workshop (YouTube):讲的是推特,但对其他也适用。国内的话很多人推荐用即刻,我玩了一段时间觉得氛围还挺好的。 ### Misc - [Julian Shapiro: How to create incredible work](https://www.julian.com/blog/craftspeople) - Start with bad then iterate to great - Narrow your scope - Design a process - Next, I create a work of my own: I use as many of the good ingredients and as few of the bad ones as possible. When in doubt, pursue what excites you. That's your **north star.** - [Bash in 100 Seconds](https://www.youtube.com/watch?v=I4EWvMFj37g) - **It's called a SHELL as it hides the kernel** - use `#!/usr/bin/env bash`. - Newton's flaming laser sword - 如果一个论点不能被数学或实验或逻辑证明,那么它根本就不值得花时间去争辩 | A philosophical [razor](https://en.wiktionary.org/wiki/razor) which states that what cannot be settled by experiment is not worth debating. - Writing: [Write 5x more but write 5x less。](https://critter.blog/2020/10/02/write-5x-more-but-write-5x-less)