MacBook, defective by design banner

title:
Put the knife down and take a green herb, dude.


descrip:

One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for www.myfreakinfirst-andlast-name.com

Using 89% of the same design the blog had in 2001.

FOR ENTERTAINMENT PURPOSES ONLY!!!
Back-up your data and, when you bike, always wear white.

As an Amazon Associate, I earn from qualifying purchases. Affiliate links in green.

x

MarkUpDown is the best Markdown editor for professionals on Windows 10.

It includes two-pane live preview, in-app uploads to imgur for image hosting, and MultiMarkdown table support.

Features you won't find anywhere else include...

You've wasted more than $15 of your time looking for a great Markdown editor.

Stop looking. MarkUpDown is the app you're looking for.

Learn more or head over to the 'Store now!

Friday, July 09, 2021

You should not have a giant, monolithic application. Monolithic codebases are the mind-killers. They are the little-deaths that bring total obliteration.

Face your monolith. Allow it to pass over you and through you...

... into a well-modularized codebase.

But here's the corollary nobody links to this practice: 

The best modularization requires smart denormalization and repetition. 

You catch some of this when you hear about microservices, which hold down the other end of the spectrum from monoliths. I think it's largely because of their association with NoSQL, where being comfortable with denormalizing schemas is absolutely essential. 

I don't want to get too deep into NoSQL right now, but the concept is the same: To be most nimble in a modularized codebase requires that you -- and it's nearly against my programming north star to admit it -- Repeat Yourself.

And that's okay. If you have one service that calls another, and you need to massage your payload, you may have to change it two places, the caller and the server.

THIS IS FINE, NO MEME-MIC DOG & FIRE SHOW REQUIRED.

Because you know what's worse than changing a model two places? Changing it three places.

  1. The shared library
  2. Updating the reference for the caller
  3. Updating the reference for the server

Those last two are too often nontrivial. Perhaps they should be trivial, but in practice they almost never are. There's always some argument that 1. requires a breaking change to the shared library, which requires we up its major version, and 2. & 3. are set to update automatically on minor changes, but not major changes, and now we have to redeploy in just the right order so that the other apps that depend on the library in 1. aren't brought down when we... I see you over there. You get it. This has happened to you too.

What were we hoping to save ourselves from again? Exactly this sort of tedium. Except now it's even worse than it was before in the monolith!

Look, you should be coding the caller and server defensively anyhow, as independent services. The payloads for both, once serialized, are probably coming across the wire as XML or JSON (please JSON) anyhow, so it's not like these model abstractions are necessary beasts. Why do we have to point them both to the same library to have a source of truth? We're comfortable consuming third party apps without sharing model definitions, and that's [at least partially] the setup you chose when you went to modular services.

Treat each app as an independent development and change the model twice -- or, when appropriate, add a new model to one with a new API endpoint to maintain backwards compatibility with other consumers. Denormalization between apps at the point of the interface is a-ok. 

I'm requesting foo. You're sending foo. Having two copies of foo, one for me and one for you, is fine. Even having a slightly different foo for each is often fine as long as the middle of the Venn is what the transaction in question is worried about.

No, for real. It's okay. Really. You'll thank me later.

Labels: , ,


posted by ruffin at 7/09/2021 05:03:00 PM
Tuesday, June 15, 2021

Okay, one thing I hate in JavaScript code is all the code around AJAX requests.

jQuery has a wrapper. And AngularJS has a wrapper. And RxJS has a wrapper. And...

Why don't we just use XMLHttpRequest? Then our services, which are often fairly UI-templating-agnostic already, can live anywhere we want.

There are at least two good reasons... The first is to handle JSONP. I'm ignoring that for now.

The second is to handle async code in a manner that's conventional for each templating engine. $http in AngularJS, for instance, uses $q in place of Promises to ensure changes are communicated correctly to AngularJS. RxJS returns things as Observables (RxJS specific, give or take) rather than Promises (which is a standard), though this is easy to work around.

It's probably worth saying that, well, first that I stole the framework for this code from a SO answer, and second that this is a great example of how to hand-roll a Promise. Just call resolve or reject when you're ready and poof, you've got a Promise.

For the most part, however, you can pass the data event back into the templating system's change detection scheme fairly easily.

So I wanted to write a quick XMLHttpRequest library one could use for the vast majority of CRUD operations. I've only got CR below (give or take. Let's not get into POST vs PUT for the time being), but you get the picture.

I'll try to edit this as I make changes. Comments welcome.

The bottom line, though, is that this is not difficult code. Why we thought we needed wrappers to make AJAX calls I'll never know. The longer you can resist context-specific code, like library-specific wrappers, the longer your original code can live.

(See Exhibit VanillaJS (or the more useful, but strangely mascoted, competing Vanilla JS project).)

Yes, this is, in contrast to my original goals, in TypeScript. It's a pretty easy port. But seriously, you should be using TypeScript.

export default class BaseService {
    getOneUntyped(url: string, id?: string): Promise<any> {
        return new Promise((resolve, reject) => {
            let request = new XMLHttpRequest();

            request.onerror = function () {
                reject(`No response was given.`);
            };

            request.onreadystatechange = function () {
                console.log(request.readyState, request.status);

                // 4 === DONE
                if (request.readyState === 4) {
                    // 200 === OK.
                    if (request.status === 200) {
                        resolve(JSON.parse(request.response));
                    } else {
                        // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status
                        // Before the request completes, the value of status is 0.
                        // Browsers also report a status of 0 in case of XMLHttpRequest errors.
                        if (request.status !== 0) {
                            reject(`${request.response} ${request.statusText}`);
                        }
                    }
                }
            };

            let urlToUse = url + (id ? id : "");
            request.open("GET", urlToUse, true);
            request.send();
        });
    }

    postOneTyped<T>(url: string, payload: T): Promise<boolean> {
        return new Promise((resolve, reject) => {
            var request = new XMLHttpRequest(); // new HttpRequest instance
            request.open("POST", url);
            request.setRequestHeader("Content-Type", "application/json");
            request.send(JSON.stringify(payload));

            request.onerror = function () {
                reject(`No response was given.`);
            };

            request.onreadystatechange = function () {
                console.log(request.readyState, request.status);

                // 4 === DONE
                if (request.readyState === 4) {
                    // 201 === Created.
                    if (request.status === 201) {
                        resolve(true);
                    } else {
                        // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status
                        // Before the request completes, the value of status is 0.
                        // Browsers also report a status of 0 in case of XMLHttpRequest errors.
                        if (request.status !== 0) {
                            reject(`${request.response} ${request.statusText}`);
                        }
                    }
                }
            };
        });
    }
}

Labels: , , ,


posted by ruffin at 6/15/2021 10:30:00 AM
Thursday, June 10, 2021

 

A flaw in the design of the Apple Silicon “M1” chip allows any two applications running under an OS to covertly exchange data between them, without using memory, sockets, files, or any other normal operating system features. This works between processes running as different users and under different privilege levels, creating a covert channel for surreptitious data exchange.

The vulnerability is baked into Apple Silicon chips, and cannot be fixed without a new silicon revision.

...

The ARM system register encoded as s3_5_c15_c10_1 is accessible from EL0, and contains two implemented bits that can be read or written (bits 0 and 1). This is a per-cluster register that can be simultaneously accessed by all cores in a cluster. This makes it a two-bit covert channel that any arbitrary process can use to exchange data with another cooperating process. A demo app to access this register is available here.

... This approach, without much optimization, can achieve transfer rates of over 1MB/s (less with data redundancy).

The original purpose of this register is unknown, but it is not believed to have been made accessible to EL0 intentionally, thus making this a silicon erratum.

 
I love stuff like illegal opcodes. As the page's author says, "Someone in Apple's silicon design team made a boo-boo. It happens. Engineers are human." Every chip has mistakes. As long as it's not damaging, and this one doesn't seem to be, what's most interesting to me is how skillfully humans are able to find them. Easter egg hunting redux.

Labels: ,


posted by Jalindrine at 6/10/2021 09:20:00 AM

I was following a video from Pinal Dave called "Who Dropped Your Table?" and was getting a message that, "Currently, this report does not have any data to show, because default trace does not contain relevant information."

I googled for the reason why and guess what? The first hit (and answer) was on sqlauthority.com:

One of my clients of Comprehensive Database Performance Health Check recently asked me if there is a way to fix the error which they get when they try to open Schema Changes History. The error was related to Not Have Any Data to Show in the report. Let us see how we can fix that.

The solution of the same is very simple and I have previously blogged about it here: SQL SERVER – SSMS: Configuration Changes History.

Here is the solution:

1
2
3
4
5
6
7
8
EXEC sp_configure 'Show Advanced Options', 1;
GO
RECONFIGURE;
GO
sp_configure 'default trace enabled', 1
GO
RECONFIGURE WITH override
GO

When you run the above command, SQL Server will first enable the advanced options. Right following that, it will enable the default trace.

And now I too can tell who dropped tables on my database.

(I do worry a little that enabling this might be a significant performance hit, but I just set it on my local dev machine for now. The answer to who messed things up will, here at least, always be me. Hopefully!)

Labels: ,


posted by ruffin at 6/10/2021 09:13:00 AM
Friday, June 04, 2021

Here's a post I put onto the story posted at StackOverflow regarding its sale to Prosus. As of this morning, it's been a day or two and it still hasn't passed moderation. I mean, that's their right, but let's say it doesn't speak well for their willingness to listen. Makes you wonder how many more critical comments are in moderation limbo.

Once this acquisition is complete, we will have more resources and support to grow our public platform and paid products, and we can accelerate our global impact tremendously.

But what ways are you unable to impact the globe now? That is, shouldn't I substitute "impact" with "financial growth" to decode this sentence?

My guess is that StackOverflow is doing exactly what we appreciate it doing right now. That is, THE GROWTH IS OVER. I couldn't care less if you spin out opportunities based on the same codebase in some wholly owned subsidiary that chases cash, but within stackoverflow.com? I can't recall anything changing over the last almost 10 years that's a knock it out of the park, revolutionary advance. And I don't need one!

There are some interesting problems that could use solving at stackoverflow.com, certainly. How do you keep answers fresh when the answers to questions change over time, and old accepts are now objectively wrong? How do you manage moderation to ensure it's not tone deaf (or worse) at times? Maybe even, "How can I pay to get an answer to my question beyond simple point bounties?" That last one is the only one I can think of quickly that increases revenue (and only as a middle banker, not as the primary entity paid) and usefulness of the site.

But growth? What growth? Are the servers crumbling over the load? No. Are there too many low quality answers? Maybe, but it's been reasonably easy to get past the chaff. Do you need a better mobile app for countries with less infrastructure? Well, actually, yes. Yes you do. But beyond that...

Convince me this isn't about the money, or -- and I'll give you a softball here -- even primarily about the money. What is this "impact" primarily about that's not income?


 

Labels: , ,


posted by ruffin at 6/04/2021 01:21:00 PM
Friday, May 28, 2021

There is no world where this is a good thing.

Before you ask, no, I have turned off autocorrect in the Keyboard preferences pane.

I hate this OS. QA is nonexistent. Maybe this isn't a native dialog and it'll end up being a Sublime Text bug, but the autocorrect is so over-pervasive and unwieldy for someone who wants to keep their hands on the keyboard with a minimum of presses -- no, you know, the real issue is that this is an iOS "feature" that's made its way, half-baked, to my desktop OS. Let. Me. Turn. It. Off. Freakin' @#$@^#$%@#$. I hate you, macOS.

ARGH. I hate this OS.


EDIT (20210610): Turns out there was an entry in the Shortcuts tab to autocorrect txt to text. I don't remember adding it, and there is at least one that Apple adds by default (omw == On my way!) but let's say I did, b/c I must have, right? 

Guess where I would've added that typing shortcut? On my iPhone; that's right.

Why would Apple think that I automatically wanted all my thumb-typing shortcuts copied over to my Mac that has a real, full-sized keyboard? 

 Insane. Though this time I should apologize to QA and go after product management instead. Wow.

Labels: ,


posted by ruffin at 5/28/2021 11:09:00 AM
Thursday, May 27, 2021

I wanted to diff two files on my Mac. I can do this in BBEdit, but it’s not super easy. You can open one, then the other, then highlight both and diff, or ch… oh, you get it.

But there’s a command line version of BBEdit’s diff tool, bbdiff. TIL that you can run it like this...

bbdiff "path/to/file1.text" "path/to/file1.text"

Note that if you don't have bbdiff installed but you do have BBEdit installed, you just need to select the menu item BBEdit >>> Install Command Line Tools...

But how do you get the daggum full path to the files in the Finder?

Seems like it might've used to do this by default, but the Finder can be set to show the path to the file you've got selected in a footer.

  1. Open Finder
  2. Click View >>> Show Path Bar
  3. Profit.

And here's the extra trick: Right click the file in that bar and select Copy [filename] as Pathname and POOF, you've got the full path in your clipboard.