Lukas Z's Blog

Knowing Is Not Enough

“Knowing is not enough, we must apply. Willing is not enough, we must do.”

– Bruce Lee

Something will stick in our brains if we read a book about webdesign. Let’s (generously) say it’s 30% of the information. We will be able to have a converstation with a web designer and, because we know some of the lingo, in the future we will be able to extract more value from blog posts, books and talks on this topic.

But it’s in the 70% we miss (or forget about) where the actual usefulness of the book was: The skill on how to design something for the web.

And this skill can (for most) only be obtained by practice. The theory is a guide but it’s not perfect. It was written to reach many people and it’s unlikely we are entirely like them. There’s something special, quirky about us that needs to be addressed.

Perhaps the book is even outdated. Technology moves fast.

But the biggest reason is determined by the way our brains work. We have to make the theory real for ourselves by practice.

Realness requires a change of perspective. Switching our role from learner to applier is such a change.

Often it’s physical. We go through the motions. We move our body. When we interact with the world can we verify if our knowledge is complete enough.

For instance, it’s very easy to say that we know about gardening. We take some vague notions, some snippets of knowledge and store them in our memory with a “mastered that” label. But when we try to take care of our garden, when we try to apply the knowledge we must take off that label and check if it was justified. We will most likely see that it was not. The label was hiding gaps in our understanding.

By doing we find the questions to ask to fill those gaps.

Naturally, this is why textbooks often have self-test questions at the end of a chapter.

Finally, let us remember that talking counts as well. Explaining something to another is a fantastic way to verify and deepen our own understanding.

There is another step that seems natural to take: Imitation, but let’s talk about it in another post.

Attention - How to Compete With Dostoevsky

After we write a book, or compose a song, or program an app we need to market it. (Ideally, thinking about marketing comes first, though.) But how can we compete?

Look at the explosion of production on Youtube. Or Spotify. On Social Media. Why should they choose our work instead of someone else’s? Why should they gives us their attention?

And we don’t only compete with all of that, but with everything that came before. “Brooklyn 99” may be a fun show to watch, but we can just as well watch old episodes of “Friends” or “Seinfeld”. We can read the new book by soandso, but we can also read Dostoevsky. In fact, we probably should read the older books, because there is a reason that something is still talked about after so many years.

One opportunity could be that the recent stuff is more relevant. Disco music was great three or four decades ago. Now our taste has changed. (I should read a new novel. I would like to know if for example the characters in current books stare at their phones all the time..)

Another chance might be to surpass the old. What if the new has a good chance of being better than the old? Because the new can use the old and pick the best parts.

Be as it may, in 2020 the choices for consumption are limitless. In every genre there are so many alternatives. Maybe there really is no reason to stick with one show, one author, one band unless we really love something about them. Their style, their philosophy or the kind of music they make.

Consistency may be key. Perhaps with some we can each time be sure that their content is good for us.

I assume many people prefer to go to McDonalds because they know that their expectations will be satisfied. Even if we try a new hamburger, we can be pretty sure it won’t be horrible. (This is of course assuming we don’t consider McDonalds food generally horrible.)

Be as it may, attention is like the Spice in the Dune novels. It’s the most valuable “substance” there is. I suppose we must earn it. And then keep earning it.

Write to Explain and Be Brief

Do you think you know something? Then explain it to someone. Do you still think you know afterwards?

– A quote I just made up

I am a freelance programmer and sometimes there are periods without work. Currently is like that. Well, almost. I still have a running project, but it’s not full time so I have much more time available.

I am using the time to read. But from time to time I see a company that I like looking for programmers. They are usually foreign companies that offer remote work. This time I have sent three applications. One was ignored and the other two required me to do some sort of writing.

One said that the cover letter is key. I’m paraphrasing here: “Show us that you can write well. We judge your programming ability by your writing.” The other required me to write a project proposal.

The first one is still undecided but the second one was rejected. It wasn’t good enough. It lacked brevity and it didn’t demonstrate convincingly that I know the subject matter well.

Jobs or no jobs, I have decided to improve my writing.

For this reason I am planning to blog more. A lot more actually. Short brief posts about various topics.

The goal: Become a better writer. (In English, which is not my first language.)

Why? Well, to get jobs of course. Okay, that is at least a part of it. The other is trying to actually become a better thinker. And to live up to my teenage self. If you asked me what I want to be at the age of 18 I would have answered: A novelist.

Lofty goals. I will see how it goes.

And also this: I have no idea if someone will read those posts (I am not tracking visitors at the moment), but if they are good, someone eventually might.

That would be cool.

CASIO W96-H: How to Turn the Hourly Beep on and Off

Here’s a quick guide on how to turn that hourly beep off with Casio W96-H.

casio w96h buttons

Guide:

  1. In normal time mode press button 1 once to get into alarm mode (“AL” in the upper left).
  2. Press button 2 once to get into the hourly time signal screen (“:00” displayed).
  3. Press button 3 to toggle the hourly signal on and off.

Anyway, here’s a link to the manual.

Wie Wir Die Einnahmen Unserer GbR (iOS App Business) Gegenüber Dem Finanzamt Erklärt Haben

Es folgt eine kurze Anleitung, wie wir die Steuern unserer 2-Mann GbR erklärt haben. Die GbR hat auf der Einnahmenseite Apple (App Store Verkäufe und Abos) und Google (AdMob Werbung) und auf der Ausgabenseite Dinge wie Serverkosten, Übersetzungen, Kauf von Grafiken usw. Die Einnahmen werden 50:50 zwischen den beiden Gesellschaftler aufgeteilt. Es gilt die Kleinunternehmerregelung.

Die Steuererklärung wurde auf elster.de erstellt und verschickt.

Wichtiger Hinweis: Dieser Blogpost ist keine Beratung eines qualifizierten Menschen, z.B. eines Steuerberaters, sondern nur der anekdotenhafte Post eines Laien ohne Anspruch auf Korrektheit. Im Zweifel bitte auf jeden Fall also Gehirn benutzen und einen Profi fragen!

Die Arbeit gliederte sich in 2 Phasen:

  1. Aufstellung aller Einnahmen und Ausgaben anfertigen. (Rechnungen, Belege und Kontoauszüge checken und beiseite legen.)
  2. Elster-Formulare ausfüllen.

Elster Formulare

Auf dem Screenshot sieht man die 4 Formulare, die wir übermittelt haben. Ich beschreibe sie nacheinander von oben nach unten.

Formular 1: Die Gewerbesteuererklärung

GbR Gewerbesteuer

Im Screenshot sieht man, was wir ausgefüllt haben (alles andere wurde entsprechend nicht ausgefüllt). Das grüne Feld: Hier Gewinn Eintragen. Gewinn = Einnahmen - Ausgaben.

Formular 2: Gesonderte und einheitliche Festellung

GbR Gesonderte und einheitliche Feststellung 1

GbR Gesonderte und einheitliche Feststellung 1

GbR Gesonderte und einheitliche Feststellung 1

Hier haben wir 2 mal (weil wir 2 Gesellschafter sind) die Anlage FB hinzugefügt. Siehe Screenshots. Blauer und grüner Kasten: Infos der Gesellschaftler eintragen. In letzten Bild sieht man auch bei “Zähler” und “Nenner”, dass wir 50:50 geteilt haben.

Formular 3: Umsatzsteuererklärung

GbR Umsatzsteuer 1

GbR Umsatzsteuer 2

Hier haben wir im Grunde nur eine 0 erklärt. Die Beträge, die angefallen sind, unterliegen nämlich sämtlich §13b UstG (Stichwort “Reverse Charge Verfahren” bzw. “Steuerschuldumkehr”). Dies ist nicht immer der Fall! Bei uns aber schon, da unsere Einnahmen alle von Google und Apple stammen und diese Firmen es (in unserem Zusammenhang jedenfalls) so machen.

Das Grüne Feld: Hier Umsatz Eintragen. (Also Gewinn + Ausgaben.)

Formular 4: Die EÜR

GbR EÜR 1

GbR EÜR 2

Last but not least die Einnahmenüberschussrechnung.

Das grüne Feld: Hier Umsatz Eintragen. (Also Gewinn + Ausgaben.)

Das blaue Feld: Hier Ausgaben Eintragen. (Also Umsatz - Gewinn.)

Das rosa Feld: Hier Gewinn Eintragen. (Also Umsatz - Ausgaben.)

Schlusswort

So, das wars. So viel ist das also gar nicht. Ich hoffe, ich konnte jemandem helfen, aber ich weise nochmal drauf hin, dass es hier erstens um einen Spezialfall geht: 2 Personen GbR, App Business, Einnahmen von Google und Apple. Und Zweitens übernehme ich ausdrücklich keine Haftung für Eure Taten, sprich Steuererklärungen. Ich bin Laie und berichte nur, wie wir das ausgefüllt haben.

Calculate Bank Loan Payments

I wrote a small tool that allows to compare different scenarios when borrowing money for buying real-estate.

There are a few parameters that can be set:

  • How much money do I need?
  • How much do I want to pay back every month?
  • For how long is the interest rate fixed? (Longer fixing = higher rate)
  • Am I planning to use unscheduled repayments? (Those are mandatory, but of course lower the cost of borrowing and shorten the time until you’re debt-free.)

The tool allows to compare different selections on three of those parameters (the loan sum is currently fixed).

Please try it out here and let me know if you like it!

Calculating the Deltas for performBatchUpdates

Poly Art Title

Time for a code snippet post.

If you have collection or table view that can change in unpredictable ways, and you wish to animate the changes, then you will usually use the performBatchUpdates method of UICollectionView and UITableView.

This method takes a block, and in that block you are expected to account for all the changes in your datasource. For instance, if the number of elements returned in a section is 20 and after the performBatchUpdates-call its 10, they you are required to either delete 10 rows or items within that block, or delete 11 and add 1, or delete 20 and add 10, and so forth. The numbers must add up, otherwise your app will crash with an NSInternatlInconsistencyException. (You can theoretically catch it but I haven’t tried because I suspect it will lead to many odd bugs if you do.)

Ok, so I googled for a generic way to calculate the necessary accounting, and I found this answer on StackOverflow.

The StackOverflow soltuon is presented in pseudocode, so here’s how it would look like with Swift.

import Foundation

enum ArrayAction {
    case added(Int)
    case deleted(Int)
    case moved(Int, Int)
    
    static func operationsPerformed(arrayBefore before: [Int], arrayAfter after: [Int]) -> [ArrayAction] {
        var arrayActions = [ArrayAction]()
        
        var map1 = [Int: Int]()
        var map2 = [Int: Int]()
        
        for (index, element) in before.enumerated() {
            map1[element] = index
        }
        
        for (index, element) in after.enumerated() {
            map2[element] = index
        }
        
        for key in map1.keys.sorted() {
            let index1 = map1[key]
            let index2 = map2[key]
            
            if index2 == nil {
                let deleteAction = ArrayAction.deleted(index1!)
                arrayActions.append(deleteAction)
            }
            else if index1 != index2 {
                let moveAction = ArrayAction.moved(index1!, index2!)
                arrayActions.append(moveAction)
                map2.removeValue(forKey: key)
            } else {
                map2.removeValue(forKey: key)
            }
        }
        
        for key in map2.keys {
            let addAction = ArrayAction.added(map2[key]!)
            arrayActions.append(addAction)
        }
        
        return arrayActions
    }
}

So as you can see there’s an enum called ArrayAction that represents the rows/items added, deleted and moved. Here’s how I use that method in my ViewController:

 private func performCollectionViewOperations(block: () -> (), completionBlock: (()->())? = nil) {
        collectionView.performBatchUpdates({
            let filteredBefore = self.dataSource.elements.map { $0.index }
            block()
            let filteredAfter = self.dataSource.elements.map { $0.index }
            
            for action in ArrayAction.operationsPerformed(arrayBefore: filteredBefore, arrayAfter: filteredAfter) {
                switch action {
                case .added(let i):
                    self.collectionView.insertItems(at: [IndexPath(item: i, section: 0)])
                case .deleted(let i):
                    self.collectionView.deleteItems(at: [IndexPath(item: i, section: 0)])
                case .moved(let i, let j):
                    self.collectionView.moveItem(at: IndexPath(item: i, section: 0), to: IndexPath(item: j, section: 0))
                }
            }
        }) { _ in
            completionBlock?()
        }
    }

And that private method is used like that:

    self.performCollectionViewOperations(block: {
        // do some adding, removing and moving of elements here.
    }) // not using the completionBlock here

And that’s pretty much it.

Where do I use this code and might it have anything to do with the title picture? Glad you asked. :P

It’s used in Poly Art, a game a friend and I made for iOS. It’s free to play and we’d be happy if you gave it a try!

(I really had a hard time calculating the deltas in the game, because I stubbornly avoided the generic solution. Instead I was trying to anticipate what changes were possible, but as any programmer sooner or later learns: Even the simplest things can get very difficult if you use just a few of them combined.)

How to Scrape Wikiquote

Wikiquote logo

I like to use the command line tool fortune which displays a random quote from a database of files.

I wanted to add quotes from Wikiquote. But it turns out that Wikiquote is difficult to parse and the API is no help. (The API gives you a structured document but the actual content is just one large wall of text that has to be parsed just like the HTML page.)

So after giving up on scraping the webpage I had the idea to use Wikiquote’s edit page.

Here’s how it works: Let’s say you want the quotes from the Richard Feynman page. Open it on your browser, tap edit, and you get the edit page for Richard Feynman.

Voilà. It’s much easier to parse! Simply take all lines from the <textarea> that start with one or two asterisks (depending if you want the source) and then filter out a few things like formatting and lines that only contain quotes. A much better scraping experience.

So yeah, that’s my idea/innovation. I wrote a scraper in ruby that you can find on Github which generates the files needed by fortune. Check out the README there.

(Wikiquote image taken from here.)

AWS: How to Change S3 Response Headers

I have S3 buckets that I use to serve public assets with (behind CloudFront, with a custom domain name). However the response always included the header field “Server” with a value “AmazonS3”. I didn’t like to publish the fact that I was using S3 (because what for?) and I wondered If I could change that string.

It’s not entirely straightforward, but not difficult either.

Here’s how I did it:

  1. Set up S3 buckets

  2. Set up CloudFront for these buckets

  3. Set up a Lambda (Lambda@Edge only, this means us-east-1, N. Virginia)

  4. Insert code (see below)

  5. Actions/publish new version (must be versioned Lambda)

  6. Select version and add CloudWatch triggers. NOTE: Make sure you use the event type “origin-response”.

  7. Ensure permissions for Lambda (logs:CreateLogStream, logs:PutLogEvents, logs:CreateLogGroup, maybe others..)

  8. Set up trust relationship for lambda role, see below.

Note that depending on you CloudFront setup it can take a while for your changes to become visible. I used the “Invaldations” feature to invalidate the cache for some urls.

Lambda Code:

'use strict';

exports.handler = (event, context, callback) => {
    const response = event.Records[0].cf.response;
    response.headers['server'] = [{
        key: 'Server',
        value: 'Whatever you like',
    }];

    callback(null, response);
};

Trust Relationships:

"Service": ["lambda.amazonaws.com", "edgelambda.amazonaws.com"]

This is all based on this useful blogpost, definitely check it out: https://nvisium.com/resources/blog/2017/08/10/lambda-edge-cloudfront-custom-headers.html