Terminologi:
Å være presis er ikke det samme som å være spesifikk.
Upresis benevning er vag. Uspesifikke utsagn snakker ikke om spesifikke hendelser.
Å være spesifikk er lurt noen ganger, og dumt andre ganger. I en feilrapport er det lurt å ta med spesifikk informasjon om hva som har skjedd.
Når vi lager abstraksjoner, er vi ute etter presisjon, ikke detaljer. Å lage abstraksjoner er i sin natur en øvelse om å ta et steg opp over alle detaljene. Hvis vi tvinger oss selv til å kun snakke om detaljene, kommer vi oss aldri opp.
I Matnyttig-koden
The strategic decisions should be goal consistent. If you wanna go somewhere, you need to make sure that target is reachable. And it won’t be unless you make sure it is.
When driven by flow, you build speed and turn where you can go the fastest. Intuition takes the front seat, analysis can wait. See how fast you can go.
Reducing WIP is the maintenance you need to enable spurts of wild charge. Clean it up. Make it consistent.
Looking back on how I first approached programming, I can’t help but laugh. It was my first year of university, and we had two math courses and a programming course. I did exercises in all three courses the same way:
My first two mandatory programming exercises went smoothly. Write the code, check it, hand it in.
On the third, I got stuck, and asked a teaching assistant. His first question left me flabbergasted. “What happens when you run your code?” I thought that was a very stupid question to ask. “There’s no use in running it. It’s not done! I have to write the rest first.”
Luckily, he persisted, and I did run the code as I’d written it. We were using MATLAB, and after I’d run my code I could look at the values of my variables. That was kind of useful!
Programming can be viewed as working with formulas.
Modern programming languages have gotten better at checking the formulas with static analysis.
A different view of programming is that programming is two parallel tasks:
I enjoy this style of interactive programming much more than the formula-oriented one. It feels more alive.
I recently spent some time exploring NATS’ wire protocol. But where should I start? I choose to start in a way that would let me interact. The official protocol demo was absolutely great for this!
First, connect to the demo server with telnet
.
telnet demo.nats.io 4222
You should see the server identifying itself with an INFO
message:
Trying 107.170.221.32...
Connected to demo.nats.io.
Escape character is '^]'.
INFO {"server_id":"NCXMJZYQEWUDJFLYLSTTE745I2WUNCVG3LJJ3NRKSFJXEG6RGK7753DJ","version":"2.0.0","proto":1,"go":"go1.11.10","host":"0.0.0.0","port":4222,"max_payload":1048576,"client_id":5089}
Then, send CONNECT {}
:
CONNECT {}
You should get an OK message back:
+OK
Finally, when the server wants to know if you’re still there (PING
), answer (PONG
):
PING
PONG
That’s it! That’s all!
I then wanted to translate what I’d learned into Clojure code using java sockets. Starting small, let’s try to connect:
import '(java.net Socket))
(let [socket (Socket. "demo.nats.io" 4222)
(
cleanup #(.close socket)]try
(:lol
finally (cleanup))))
(;; => :lol
Nice!
I aim to take very small steps, and make sure I can run an expression to see if my assumptions still hold.
Next, I want to get the INFO
message.
import '(java.io InputStreamReader BufferedReader)
(
'(java.net Socket))let [socket (Socket. "demo.nats.io" 4222)
(
reader (BufferedReader. (InputStreamReader. (.getInputStream socket)))do (.close reader)
cleanup #(
(.close socket))]try
(
(.readLine reader)finally (cleanup))))
(;; => "INFO {\"server_id\":\"NDIESIZS7MWBAOM2BVC3YLEGJLGFMZYVO23OIYB7WUF5736JWKND5TKQ\",\"server_name\":\"us-south-nats-demo\",\"version\":\"2.11.2\",\"proto\":1,\"git_commit\":\"55efd1d\",\"go\":\"go1.24.2\",\"host\":\"0.0.0.0\",\"port\":4222,\"headers\":true,\"tls_available\":true,\"max_payload\":1048576,\"jetstream\":true,\"client_id\":7640,\"client_ip\":\"89.8.1.56\",\"nonce\":\"QmdtypKmZykViYY\",\"xkey\":\"XCGB5DG4FLF6EA566VUEPSSHY4GLNKHEX4VYQD3SLZRZU4KUHWSQ2PHH\"} "
Yay! Finding the right classes and methods from java.io
og java.net
took a bit of time, but taking
super-small steps kept me grounded and focused. “Many More Much Smaller
Steps”, says
GeePaw Hill. Smaller steps give smaller errors, and we can correct
our course quickly.
Now, let’s send the CONNECT {}
and see
if we can get an +OK
back.
import '(java.io DataOutputStream InputStreamReader BufferedReader)
(
'(java.net Socket))let [socket (Socket. "demo.nats.io" 4222)
(
reader (BufferedReader. (InputStreamReader. (.getInputStream socket)))
writer (DataOutputStream. (.getOutputStream socket))do (.close reader)
cleanup #(
(.close socket))]try
(assert (str/starts-with? (.readLine reader) "INFO")
("Expect INFO message from NATS server")
"CONNECT {}\r\n")
(.writeBytes writer flush writer)
(.
(.readLine reader)finally (cleanup))))
(;; => "+OK"
What we expect! I add asserts to force early errors if I break code I got working earlier.
Now, time for the PING
and PONG
. These involve waiting, so they are a bit
tricky. First, I want to see if I can get any PING
whatsoever out of my system.
import '(java.io DataOutputStream InputStreamReader BufferedReader)
(
'(java.net Socket))let [socket (Socket. "demo.nats.io" 4222)
(
reader (BufferedReader. (InputStreamReader. (.getInputStream socket)))
writer (DataOutputStream. (.getOutputStream socket))do (.close reader)
cleanup #(
(.close socket))]try
(assert (str/starts-with? (.readLine reader) "INFO")
("Expect INFO message from NATS server")
"CONNECT {}\r\n")
(.writeBytes writer flush writer)
(.assert (= (.readLine reader) "+OK"))
(
(.readLine reader)finally (cleanup))))
(;; => "PING"
Fantastic! Interactive programming sure is a great way to provide a daily dose of dopamine.
Now things get a bit tricky. We want to wait a total of 3 seconds, and print all the readLines we got. Why is that tricky?
We don’t know in advance how many readLines we need to make.
🤔
Reading the BufferedReader docs, we find the .ready method. Awesome!
import '(java.io DataOutputStream InputStreamReader BufferedReader)
(
'(java.net Socket))let [socket (Socket. "demo.nats.io" 4222)
(
reader (BufferedReader. (InputStreamReader. (.getInputStream socket)))
writer (DataOutputStream. (.getOutputStream socket))do (.close reader)
cleanup #(
(.close socket))]try
(assert (str/starts-with? (.readLine reader) "INFO")
("Expect INFO message from NATS server")
"CONNECT {}\r\n")
(.writeBytes writer flush writer)
(.assert (= (.readLine reader) "+OK"))
(3000)
(Thread/sleep let [lines (atom [])]
(loop []
(if (.ready reader)
(do (swap! lines conj (.readLine reader))
(recur))
(@lines)))
finally (cleanup))))
(;; => ["PING"]
I think I maybe just want files in a folder - but if it’s just files, I want a nice, working backup.
How can I get those files archived and synced into my Nextcloud?