Jekyll2023-02-20T16:04:41-06:00https://www.jakerobers.com/feed/software.xmlThe Software Scrutiny Journal | SoftwareThe most pragmatic software leverages existing tools. By standing on the shoulders of giants, you too can win at business.The useSelector Anti-Pattern2021-10-20T00:00:00-05:002021-10-20T00:00:00-05:00https://www.jakerobers.com/useSelector-anti-pattern<p>There are two common ways of accessing a redux store: either by <code class="language-plaintext highlighter-rouge">connect</code> or <code class="language-plaintext highlighter-rouge">useSelector</code>.
I will discuss why I prefer <code class="language-plaintext highlighter-rouge">connect</code> and why you should too.
Simply put, <code class="language-plaintext highlighter-rouge">useSelector</code> makes life unnecessarily difficult due to coupling.</p>
<p>Consider a user profile page that displays data from the redux store.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// UserProfilePage.tsx
import React from "react";
import { useSelector } from "react-redux";
import type { State } from "./Store";
export function UserProfilePage() {
const user = useSelector((state: State) => state.user);
return (
<div>
Name: <span data-testid='name'>{user.firstName} {user.lastName}</span>
<br />
Occupation: <span data-testid='occupation'>{user.occupation}</span>
<br />
Hobbies: <span data-testid='hobbies'>{user.hobbies.join(', ')}</span>
<br />
</div>
);
}
</code></pre></div></div>
<p>It’s pretty simple:</p>
<ol>
<li>Fetch the user data</li>
<li>Display the user information</li>
</ol>
<p>Woah, hold on a minute.
Aren’t there two concerns here?
We fetch the user information in one step, and in another we display the information.
Notice that by using hooks, this coupling is <em>inescapable</em>.</p>
<p>Though this may seem pedantic at the surface, let’s explore this thought a little further.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// UserProfilePage.tsx
import React from "react";
import { useFetchUser } from "./useFetchUser";
export function UserProfilePage() {
const user = useFetchUser(); // <-- Changed hook call
return (
<div>
Name: <span data-testid='name'>{user.firstName} {user.lastName}</span>
<br />
Occupation: <span data-testid='occupation'>{user.occupation}</span>
<br />
Hobbies: <span data-testid='hobbies'>{user.hobbies.join(', ')}</span>
<br />
</div>
);
}
</code></pre></div></div>
<p>I’m not sure how you feel about this, but this makes me very uncomfortable.
Whether we are fetching this data from the redux store or from an API, we should not tie the data retrieval and display together.
Furthermore, writing a unit test for UserProfilePage becomes difficult too.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// UserProfile.test.tsx
import { createStore } from "redux";
import { Provider } from "react-redux";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { UserProfilePage } from "./UserProfilePage";
const initialState = {
user: {
firstName: "Jake",
lastName: "Robers",
occupation: "Software Engineer",
hobbies: ["Brewing Beer"],
},
};
describe("UserProfile", () => {
test("renders with the user's name", async () => {
const store = createStore((state) => state, initialState);
render(
<Provider store={store}>
<UserProfilePage />
</Provider>
);
expect(screen.getByTestId("name")).toHaveTextContent("Jake Robers");
});
});
</code></pre></div></div>
<p>Take one more look at the test description:</p>
<blockquote>
<p>renders with the user’s name</p>
</blockquote>
<p>So if we are checking if “Jake Robers” appears in the component, why on earth are we constructing an entire redux store?
Doesn’t this seem pretty unnecessary?
I sure think so.
Let’s have another try at the implementation.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// UserProfilePage.tsx
import React from "react";
import { connect } from "react-redux";
import type { User } from "./Store";
export const UserProfilePage = ({ user }: { user: User }) => (
<div>
Name:{" "}
<span data-testid="name">
{user.firstName} {user.lastName}
</span>
<br />
Occupation: <span data-testid="occupation">{user.occupation}</span>
<br />
Hobbies: <span data-testid="hobbies">{user.hobbies.join(", ")}</span>
<br />
</div>
);
export default connect((state) => ({ user: state.user }))(UserProfilePage);
</code></pre></div></div>
<p>This is much better because our two responsibilities are split into two components:</p>
<ol>
<li>UserProfilePage component</li>
<li>The connected component returned from react-redux</li>
</ol>
<p>The good news doesn’t end here.
Look at how clean our unit test becomes!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import { UserProfilePage } from "./UserProfilePage";
const user = {
firstName: "Jake",
lastName: "Robers",
occupation: "Software Engineer",
hobbies: ["Brewing Beer"],
};
describe("UserProfile", () => {
test("renders with the user's name", async () => {
render(<UserProfilePage user={user} />);
expect(screen.getByTestId("name")).toHaveTextContent("Jake Robers");
});
});
</code></pre></div></div>
<p>The redux store is now completely yanked out.
This makes sense because we never intended to test the redux store.
Recall that the purpose of the test is to check the presence of the name.
If more coverage is needed, I’d begin with testing the state selectors by abstracting user selection into a separate function.
<em>If even more coverage is needed</em>, only then I would reach for constructing the redux store and render a connected component.
Often this is overkill though.
By the time you need connected component tests, you probably already have Selenium or Cypress running.</p>
<p>My conjecture: <em>All hooks that reach for a context is harmful.</em></p>
<p>There’s still a place for hooks though.
Most of the core React hooks manage local state – this is the way it should be.
Remember that hooks are the shiny new toy in React, and that the jury is still out on “best practices”[1].
Use with caution.</p>
<p>I’m not the only one with this hot-take.
There was a Github issue filed a while back <a href="https://github.com/reduxjs/react-redux/issues/1404">expressing similar concern</a>.</p>
<p>[1] I’m not a huge fan of the term “best pratices” in the software world
because it leads to the notion that there’s only one right solution. This
is harmful in that development teams will stop all further analysis and not
correctly determine the benefits and drawbacks. Whether or not my opinion
in this article is considered “best practice” is up to you. Look into the
benefits and drawbacks to come up with your own conclusion.</p>Jake RobersThere are two common ways of accessing a redux store: either by connect or useSelector. I will discuss why I prefer connect and why you should too. Simply put, useSelector makes life unnecessarily difficult due to coupling.Separate Concerns with Higher Order Components2021-03-31T00:00:00-05:002021-03-31T00:00:00-05:00https://www.jakerobers.com/separate-concerns-with-higher-order-components<p>Higher order components (commonly known as HoC) are a great tool for separation of concerns.
This pattern is highly encouraged in the React world and are commonly found in dependencies such as <code class="language-plaintext highlighter-rouge">react-redux</code>.
A higher order component can assist in splitting UI from data sources and mutate functionality.</p>
<p>Let’s take a look at an example where we have a to-do list.
The data is fetched via an ordinary react hook, and the results are rendered.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function TodoList() {
const todoItems = useLocalTodoItems();
return (
<div>
{todoItems.map(item => <TodoItem item={item} />)}
</div>
)
}
</code></pre></div></div>
<p>There’s a common saying in the world of software:</p>
<blockquote>
<p>A function should do one thing, and one thing well.</p>
</blockquote>
<p>You’ve heard of this phrase, right?</p>
<p>Well, this React component <em>is</em> a function.
How many concerns are in this function?
I’d like to make the argument that it is indeed doing two things, not one.</p>
<ol>
<li>Fetches the data from some specific source</li>
<li>Renders the items in the user interface</li>
</ol>
<p>It doesn’t really matter how exactly we are fetching the to-do items.
They could be fetched from an API, local storage, etc.
What matters is that the fetching and rendering co-exists.</p>
<p>So now let’s split this up!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function TodoList(props: {items: Array<Item>}) {
return (
<div>
{props.items.map(item => <TodoItem item={item} />)}
</div>
)
}
function App() {
const todoItems = useLocalTodoItems();
return <TodoList items={todoItems} />
}
</code></pre></div></div>
<p>You might be saying right now: “Jake, this is pedantic. Why would we go through the effort of moving the hook out of <code class="language-plaintext highlighter-rouge"><TodoList /></code>?”</p>
<p>My go-to counter argument in this case would be:</p>
<ol>
<li>If you introduce a nifty tool like Storybook, it’s useful to pass in a
list stub for testing UI edge-cases.</li>
<li>A component is more reusable when it has one, simple job. Imagine you’d
like to render <code class="language-plaintext highlighter-rouge"><TodoList /></code> in five different places. Would the list
always use the same data source? I wouldn’t bet on it. (See #1)</li>
<li>Unit testing <code class="language-plaintext highlighter-rouge"><TodoList /></code> becomes more difficult. How would you test
<code class="language-plaintext highlighter-rouge"><TodoList /></code> if it made a request to an API? I am personally not fond
of monkey-patching functions during the test runtime. They tend to lead
to:
<ul>
<li>mocks clobbering each other when ran asynchronously.</li>
<li>return values of the actual function changing and the mock goes stale.</li>
</ul>
</li>
</ol>
<p>For these reasons, I find dependency injection preferable.</p>
<p>If you take another look at the above code, you might notice that we are shifting “the problem” from one component to another.
Now the data-fetching responsibilities are added to the parent component.
Some might argue that it is okay if the complexity is being shifted to the root <code class="language-plaintext highlighter-rouge">App</code> component.
If the application is simple enough, I don’t see the harm in following this.</p>
<p>In some cases, <code class="language-plaintext highlighter-rouge">App</code> could be a little more bloated.
Let’s maybe look at another potential refactor.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface TodoListProps { items: Array<Item> };
function TodoList(props: TodoListProps) {
return (
<div>
{props.items.map(item => <TodoItem item={item} />)}
</div>
)
}
function withTodoItems(Comp: FC<{items: Array<Item>}>) {
return (props: {}) => {
const todoItems = useLocalTodoItems();
return <Comp items={todoItems} />
}
}
function App() {
const List = withTodoItems(TodoList);
return <List />
}
</code></pre></div></div>
<p>“Aren’t we just making this unnecessarily complicated?”
The answer very-well could be “yes”.
Though keep in mind how trivial this application is.</p>
<p>For an application that is quite large, this might be perfectly reasonable.
Perhaps the <code class="language-plaintext highlighter-rouge"><TodoList /></code> component is several hundred lines long.
Maybe the data fetching function is also just as complicated.
This is where programming becomes more of an art than a science.
I feel that maintaining separation between data fetching and presentation is worthwhile because it gives breathing room for future features.</p>
<h2 id="more-sources-of-data">More Sources of Data</h2>
<p>What if our data fetching logic now needs to support many sources?
Suppose that there are three sources: local, homegrown API, and a third party.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface TodoItems {items: Array<Item>};
function withTodoItemsSource(Comp: FC<TodoItems>, fetchTodoItems: () => Array<Item>) {
return (props: {items?: Array<Item>}) => {
const restItems = props.items || [];
const todoItems = fetchTodoItems();
return <Comp
{...props}
items={[...todoItems, ...restItems]}
/>
}
}
function withLocalTodoItems(Comp: FC<TodoItems>) {
return withTodoItemsSource(Comp, useLocalTodoItems);
}
function withAPITodoItems(Comp: FC<TodoItems>) {
return withTodoItemsSource(Comp, useAPITodoItems);
}
function withThirdPartyTodoItems(Comp: FC<TodoItems>) {
return withTodoItemsSource(Comp, useThirdPartyTodoItems);
}
function App() {
const List = withThirdPartyTodoItems(
withAPITodoItems(
withLocalTodoItems(
TodoList
)
)
);
return <List />
}
</code></pre></div></div>
<p>The power of the higher order component is realized when we stack these functions; mixing and matching across a nice spectrum.
You’ll also notice we earned a good amount of re-usability with the <code class="language-plaintext highlighter-rouge">withTodoItemsSource</code> higher order component.
Who says a higher order component has to be one layer deep?</p>
<h2 id="adding-authentication">Adding Authentication</h2>
<p>Perhaps this <code class="language-plaintext highlighter-rouge"><TodoList /></code> should hide behind authentication.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function withAuthentication<T>(Comp: FC<T>) {
return (props: T) => {
const isAuthenticated = useAuthentication();
if (isAuthenticated) {
return <Comp {...props} />
} else {
return <Login />
}
}
}
function App() {
const List = withAuthentication(TodoList);
return <List items={['one', 'two', 'three']}/>
}
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">withAuthentication</code> can easily be re-used the rest of the application.
Protecting a component behind authentication doesn’t get easier than utilizing a higher order component.</p>
<p>Also, notice the more “meta” detail buried in this example.
We are passing our own stubbed list into this component.
This demonstrates that our data fetching (<code class="language-plaintext highlighter-rouge">withLocalTodoItems</code>, <code class="language-plaintext highlighter-rouge">withAPITodoItems</code>, <code class="language-plaintext highlighter-rouge">withThirdPartyTodoItems</code>) is decoupled.</p>
<h2 id="an-alternate-reality">An Alternate Reality</h2>
<p>I’d typically end the blog post here, but maybe we can take a look at an
alternate reality.</p>
<p>What if <code class="language-plaintext highlighter-rouge"><App /></code> didn’t have separation of concerns?
What if the higher order components were omitted?
Let’s see a more tightly coupled version of the above.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function UnorderlyApp() {
const localItems = useLocalTodoItems();
const apiItems = useAPITodoItems();
const thirdPartyItems = useThirdPartyTodoItems();
const isAuthenticated = useAuthentication();
const items = [...localItems, ...apiItems, ...thirdPartyItems];
return isAuthenticated ? <TodoList items={items} /> : <Login />
}
</code></pre></div></div>
<p>Let’s see how many concerns the <code class="language-plaintext highlighter-rouge"><UnorderlyApp /></code> contains:</p>
<ul>
<li>local fetching</li>
<li>API fetching</li>
<li>third party fetching</li>
<li>authentication</li>
<li>Controlling the view based on authentication status</li>
</ul>
<p>Wow! That’s a lot of coupling.
Without a doubt, this issue will only compound overtime as more features are added.
Let’s throw another wrench in the situation:</p>
<blockquote>
<p>As a user, I would like to disable the third party todo items.</p>
</blockquote>
<p>Following this coupled <code class="language-plaintext highlighter-rouge"><App /></code>, this would look like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function UnorderlyApp() {
const localItems = useLocalTodoItems();
const apiItems = useAPITodoItems();
const isThirdPartyEligible = useThirdPartyEligibility();
let thirdPartyItems = [];
if (isThirdPartyEligible) {
thirdPartyItems = useThirdPartyTodoItems();
}
const isAuthenticated = useAuthentication();
const items = [...localItems, ...apiItems, ...thirdPartyItems];
return isAuthenticated ? <TodoList items={items} /> : <Login />
}
</code></pre></div></div>
<p>Not only is the complexity growing, but there’s an error in the above code.
Can you spot it?</p>
<p>React forbids hooks inside conditionals.
This can lead to undefined behavior if a hook is ran in one render cycle, but not the next.</p>
<p>Moving back to the HoC solution, let’s merge the eligibility check with the third party hook.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function withThirdPartyTodoItems(Comp: FC<TodoItems>) {
return (props: {items?: Array<Item>}) => {
const isEligible = useThirdPartyEligibility();
if (isEligible) {
return withTodoItemsSource(Comp, useThirdPartyTodoItems)(props);
} else {
return withTodoItemsSource(Comp, () => [])(props);
}
}
}
function App() {
const List = withAuthentication(
withThirdPartyTodoItems(
withAPITodoItems(
withLocalTodoItems(
TodoList
)
)
)
);
return <List />
}
</code></pre></div></div>
<p>The augmentation to <code class="language-plaintext highlighter-rouge">withThirdPartyTodoItems</code> leaves <code class="language-plaintext highlighter-rouge"><App /></code> untouched.
<code class="language-plaintext highlighter-rouge">withThirdPartyTodoItems</code> could easily be used many times across the app.
By isolating the change to this function, we can safely make this update without touching any of the callers.</p>
<p>My favorite part of using HoC wrapped hooks is that they become easily testable.
In fact, there is no need to mock anything (even the third party items).
All we would have to do is <a href="/avoid-vendor-lock-in-with-interfaces">dependency inject polymorphic behavior</a>.
This would even allow you to change third party services on a dime!</p>jake robersHigher order components (commonly known as HoC) are a great tool for separation of concerns. This pattern is highly encouraged in the React world and are commonly found in dependencies such as react-redux. A higher order component can assist in splitting UI from data sources and mutate functionality.Wireguard Configurations2020-12-15T00:00:00-06:002020-12-15T00:00:00-06:00https://www.jakerobers.com/wireguard-configurations<p>Lately I’ve been getting more into the self-hosted game.
This DIY approach has given me a much greater appreciation for good software.
It doesn’t come without its drawbacks though.
Self-hosting requires more time, responsibility, planning, and various chores: data back-ups, log monitoring, ensuring good up-times, etc.
It’s not a hobby to choose lightly!</p>
<p>If you decide to dabble with self-hosting, I recommend to start with a VPN.
This can help secure an architecture from the start.
While a VPN isn’t a one-stop-solution for all security problems, it does limit the attack surface.
Plus, there’s the option to assign static IPs to all connected devices – giving complete control over the subnet.</p>
<p>Some things that I self host are:</p>
<ul>
<li>This website</li>
<li>Rental applications for my properties (each LLC has its own)</li>
<li>Email</li>
<li>Git server</li>
<li>Syncthing nodes (on VPN)</li>
<li>Mediawiki (on VPN)</li>
<li>Plex (on VPN)</li>
</ul>
<p>For setting up Wireguard, there’s a couple of different types of peer configurations:</p>
<ul>
<li>The gateway</li>
<li>A server for fulfilling requests (in my case, behind NAT)</li>
<li>A client that connects to a server</li>
</ul>
<h2 id="forwarding-requests">Forwarding Requests</h2>
<p>For any devices serving requests, you will need to forward requests:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sysctl -w net.ipv4.ip_forward=1
</code></pre></div></div>
<p>According to the Arch Wiki, you can make this persist by adding the above to <code class="language-plaintext highlighter-rouge">etc/sysctl.d/99-sysctl.conf</code>.</p>
<h2 id="gateway-configuration">Gateway Configuration</h2>
<p>With the right Wireguard configuration and some firewall adjustments, you will be off to the races!
You’ll notice that there is a lot of redacted info in these configurations.
This speaks to Wireguard’s conciseness.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Address = 10.10.1.1/24
PrivateKey = (hidden)
ListenPort = 5555
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer] # Linux desktop
PublicKey = <<Pubkey of desktop>>
# Restrict this peer to solely 10.10.1.2 (32 subnet means 1 ip)
# This AllowedIps acts as a routing table. When we need to route traffic
# We will look up the public key by ip, encrypt, and then send to the endpoint.
# The endpoint is the last known point of access from the last connection
AllowedIPs = 10.10.1.2/32
[Peer] # raspberry pi server behind NAT
PublicKey = <<Pubkey of pi>>
AllowedIPs = 10.10.1.3/32
</code></pre></div></div>
<h2 id="linux-desktop-config">Linux Desktop Config</h2>
<p>This one is simple since we are not servicing requests from inside the VPN.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
Address = 10.10.1.2/24 # Matches address in gateway config
ListenPort = 38820
PrivateKey = (hidden)
[Peer]
PublicKey = <<Gateway public key>>
Endpoint = <<IP address of public server/gateway>>
# Only route requests from 10.10.1.* through our remote peer.
AllowedIPs = 10.10.1.0/24
</code></pre></div></div>
<h2 id="raspberry-pi-config">Raspberry Pi Config</h2>
<p>Besides the typical configuration, we also need the ip tables configured.
This can be seen in the PostUp and PostDown. Finally, I specify a
persistent keep alive timeout. The alternative would be to forward a port
on your router. If you choose to do this, be sure to whitelist this to the
gateway host only!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Interface]
Address = 10.10.1.3/24 # Matches address in gateway config
ListenPort = 38820
PrivateKey = (hidden)
# Punches a hole in the firewall to accept connections on the VPN network
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <<Gateway public key>>
Endpoint = <<IP address of public server/gateway>>
# We are behind a NAT, so we need a keep alive heartbeat. Otherwise the
# connection will drop.
# If you want a less chatty solution, you can port forward for the gateway.
PersistentKeepalive = 15
# Only route requests from 172.16.0.* through our remote peer.
AllowedIPs = 10.10.1.0/24
</code></pre></div></div>
<p>I could create an Ansible playbook for this, but I tend to subscribe to the YAGNI principle.
If I do need to reconfigure something like this, I now have this post to reference :)
I hope this helps you as well!</p>Jake RobersLately I’ve been getting more into the self-hosted game. This DIY approach has given me a much greater appreciation for good software. It doesn’t come without its drawbacks though. Self-hosting requires more time, responsibility, planning, and various chores: data back-ups, log monitoring, ensuring good up-times, etc. It’s not a hobby to choose lightly!The Best Way to Avoid Merge Conflicts2020-11-07T00:00:00-06:002020-11-07T00:00:00-06:00https://www.jakerobers.com/avoid-merge-conflicts<p>We all dislike merge conflicts.
Even worse, convoluted merge conflicts that when improperly resolved, lead to regressions.
What is the best action towards transforming a codebase into easy-to-resolve (or zero) conflicts?
I argue for <a href="https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html">Separation of concerns</a>.</p>
<p>The Clean Code Blog quotes a publication from 1972 by David L. Parnas:</p>
<blockquote>
<p>We propose … that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.</p>
</blockquote>
<p>Consider the implications in regards to merge conflicts.
If a module has <em>only one reason</em> to change, how often is it that two concurrent pull requests collide?
Likely never.
Moreover, any possible merge conflicts would be much easier to resolve.</p>
<p>Most common, deeply nested code can be a source for messy merge conflicts.
Next time one is encountered, maybe try abstracting each bit out into a module.
Just make sure that the module encasulates a singular design decision.</p>Jake RobersWe all dislike merge conflicts. Even worse, convoluted merge conflicts that when improperly resolved, lead to regressions. What is the best action towards transforming a codebase into easy-to-resolve (or zero) conflicts? I argue for Separation of concerns.Law of Demeter: Always Encapsulate2020-10-28T00:00:00-05:002020-10-28T00:00:00-05:00https://www.jakerobers.com/law-of-demeter-always-encapsulate<p>Exposing internal data structures are bound to lead to a messy situation.
When a method caller knows too much about an underlying system, there is a nasty kind of coupling.
This dependency issue is relates to the <a href="https://en.wikipedia.org/wiki/Law_of_Demeter">Law of Demeter</a>.
Some people call it “too many dots syndrome” because it leads to <code class="language-plaintext highlighter-rouge">code.accessing.attributes.very.deeply</code>.
The whole point is that the caller should not have knowledge of the deep attribute.</p>
<p>I recently worked on a project that dealt with parsing numbers.
Lots of projects do this, right?
Another common use-case is to provide some error messages based on the type of incorrect input.
Unfortunately, as the business requirements grew, our code lagged behind.
This technical debt slowed future velocity and introduced some errors.
Eww.</p>
<p>The data structure began something like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const product = {
id: 1,
name: 'Supreme Vacuum Cleaner',
quantity: 3,
price: 100
};
</code></pre></div></div>
<p>The above structure represents what a user might have in their shopping cart for checkout.
It does feel like <code class="language-plaintext highlighter-rouge">quantity</code> isn’t actually part of a product and that it should be normalized somehow.
Regardless, this was the structure that the team came to a consensus on.</p>
<p>As it turns out, the quantity attribute needs to be a little more complicated.
You see, a user might want to input a quantity in a text box.
So at this point you might be saying: “Well just make quantity a string”.
Sounds good, right?
Let’s go ahead and roll with it.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const product = {
id: 1,
name: 'Supreme Vacuum Cleaner',
quantity: '3',
price: 100
};
</code></pre></div></div>
<p>Now the user is able to edit the quantity.
However, changing the type from a number to a string leads to an issue.
Now we cannot calculate the total as easily anymore.
So at this point you might be saying: “Just parse the number”.
<em>Hmm… there’s a little voice in the back of my head warning me</em>.
Eh, whatever.
Let’s go ahead and throw this change in too.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const product = {
id: 1,
name: 'Supreme Vacuum Cleaner',
quantity: '3',
price: 100
};
const total = Number(product.quantity) * product.price;
</code></pre></div></div>
<p>You’ll also notice that we are adding some scope in.
We are fetching the total by parsing the quantity and multiplying by the price.
I think now the little voice is speaking up:</p>
<blockquote>
<p>“Should it be the job of the caller to calculate the price?”</p>
</blockquote>
<p>If two product attributes are being exclusively accessed, maybe we should have a separate module to encapsulate this logic.
The module could be named <code class="language-plaintext highlighter-rouge">Product</code>, and it could hold a function called <code class="language-plaintext highlighter-rouge">getTotal()</code>.</p>
<p>In the favor of moving fast, we ended up <em>not</em> creating a module dedicated to product operations.
I would say that this was the first substantial warning sign that went unnoticed.</p>
<p>Alright, so now what happens when the user inputs garbage like “asdf” into the text box?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>> Number('asdf');
NaN
</code></pre></div></div>
<p>It’s not the best UX to be displaying NaN as a product total.
It’s probably a good call to add a little more safety around quantity.
This will allow us to report to the user that they’ve made a mistake.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const product = {
id: 1,
name: 'Supreme Vacuum Cleaner',
quantity: {
raw: '3',
value: 3,
isValid: true,
},
price: 100
};
let total;
if (product.quantity.isValid) {
total = Number(product.quantity.value) * product.price;
} else {
total = "$--";
}
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">const total</code> assignment is getting larger.
This doesn’t look too good.</p>
<p>At this point, the total calculation was abstracted away into a function, but it still was not apart of a module specific to a product.
This lead to several places in the codebase with similar quantity parsing.
As a result, we still <em>did not have a single source of truth</em>.</p>
<p>In the meantime, there’s an edge case that we didn’t cover yet.
Can you find it?
It’s not quite possible to purchase negative vacuum cleaners.
Additionally, there are only so many vacuum cleaners in stock.
Therefore, there needs to be some kind of minimum/maximum validation.
As a result, we added a validation object to quantity.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>const product = {
id: 1,
name: 'Supreme Vacuum Cleaner',
quantity: {
raw: '3',
value: 3,
validation: {
isValid: true,
errMessage: ""
}
},
price: 100
};
let total;
if (product.quantity.validation.isValid) {
total = Number(product.quantity.value) * product.price;
} else {
total = "$--";
}
let error = null;
if (!product.quantity.validation.isValid) {
error = product.quantity.validation.errMessage;
}
</code></pre></div></div>
<p>I am omitting the actual validation logic, but we can pretend that the <code class="language-plaintext highlighter-rouge">errMessage</code> will be populated with the expected value.
As we can see, our data structure is nowhere near as simple as it once was.</p>
<p>This is where things started breaking.
The attribute <code class="language-plaintext highlighter-rouge">product.quantity.isValid</code> has now moved to <code class="language-plaintext highlighter-rouge">product.quantity.validation.isValid</code>.
I believe we had about 10-15 callers trusting the integrity of the product data structure.
This is where the lesson is learned.</p>
<p><strong>Never trust the integrity of a data structure.</strong></p>
<p>This Law of Demeter violation could be pretty easily patched with a module dedicated to the product.
By having a module along side the complex data structure, we can derive a product’s attributes without requiring knowledge of the underlying implementation.</p>
<p>Furthermore, by following the Single Responsibility Principle, a product can be further split into a couple of concepts:</p>
<ul>
<li>Product (as before)</li>
<li>Quantity</li>
<li>Validator</li>
</ul>
<p>The first step would be to define some interfaces representing these concepts.
The interface ought to be unchanging – serving as a contract.
This will ensure stability for the 10-15 areas which utilize this code.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Validator {
isValid: () => bool,
getErrorMessage: () => string
}
interface Quantity {
isValid: () => bool,
getRaw: () => string,
getValue: () => number,
}
interface Product {
id: number,
getQuantity: Quantity,
setQuantity: (string) => void,
getPrice: () => number,
getTotal: () => number,
}
</code></pre></div></div>
<p>Take a peek and notice <code class="language-plaintext highlighter-rouge">Quantity.isValid()</code>.
From an implementation standpoint, Quantity would just defer to <code class="language-plaintext highlighter-rouge">validation.isValid()</code>.
In the case that business requirements become more difficult, additional logic can be added as necessary.</p>
<p>As a result, the 10-15 function calls can let out a sigh of relief.
They no longer need to know how exactly a quantity is being validated.
All the need to know is <code class="language-plaintext highlighter-rouge">product.getQuantity().isValid()</code>.
Easy right?</p>
<p>One other thing worth noting, we now have a <code class="language-plaintext highlighter-rouge">getTotal()</code> on Product!
Now there is a single source of truth for computing a price. :D</p>
<p>Why is encapsulation so powerful?
It is adaptive to change.</p>
<ul>
<li>The Product might one day substitute the <code class="language-plaintext highlighter-rouge">name</code> attribute for an <code class="language-plaintext highlighter-rouge">InternationalizedString</code> object. This would add support for different languages.</li>
<li>The Product might one day change <code class="language-plaintext highlighter-rouge">price</code> to a <code class="language-plaintext highlighter-rouge">CurrencyAmount</code>. This would add support for formatting price strings in different currencies.</li>
</ul>
<p>All the while, each of the 10-15 callers would be none-the-wiser on how these attributes are computed!
The interface remains a steadfast contract!</p>
<p>So next time you see <code class="language-plaintext highlighter-rouge">code.accessing.attributes.very.deeply</code>, let’s stop for a moment to refactor.
Encapsulation is a quick win.
Yes it might be a little more verbose, but you will thank yourself down the road.</p>Jake RobersExposing internal data structures are bound to lead to a messy situation. When a method caller knows too much about an underlying system, there is a nasty kind of coupling. This dependency issue is relates to the Law of Demeter. Some people call it “too many dots syndrome” because it leads to code.accessing.attributes.very.deeply. The whole point is that the caller should not have knowledge of the deep attribute.JS Performance: Classes vs Function Composition2020-10-20T00:00:00-05:002020-10-20T00:00:00-05:00https://www.jakerobers.com/js-performance-class-vs-composition<p>Today during lunch I wrote up a really quick benchmark comparing the
performance of class instantiation vs function composition.</p>
<p>Overview: Class instantiation is about 25x faster than function
composition.</p>
<p>The benchmarks below represent the time it takes to do 10,000,000
executions. 10 trial runs were completed where you can find the min, max,
and average spanning the trial runs.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>----- CLASS -----
min: 5.5ms
max: 13.9ms
average: 7.5ms
----- FUNCT -----
min: 159.3ms
max: 201.5ms
average: 170.1ms
</code></pre></div></div>
<p>What implications does this have? Typically in a Redux store, the selectors
portion of the codebase use function composition. From this basic
benchmark, it would suggest that having a read-only class solely with
getter functions would be preferable to what’s currently the standard. Note
that this does not take memoization into account since both function
composition and getter methods can support caching.</p>
<p>Why is this important? Selector code is ran a lot since it is part of a
components lifecycle. Each render has the potential to re-run selector
code. When given hundreds or thousands of selectors in a mature codebase,
performance slowly becomes an issue. Also consider that selectors can
functionally compose other selectors (which can compose even more
selectors).</p>
<p>Is it that big of a deal? Probably not. In a mature codebase, there is most
definitely larger fish to fry when improving performance. In any case, I
hope this read was interesting.</p>
<p>Benchmark code can be found in my <a href="https://git.jakerobers.com/?p=react-class-fn-comp-performance;a=blob;f=src/App.js;h=1d61f9bf1cc6122c57f7826ae5a6fda43720bd52;hb=1e6e9d46e01eed9f40d57e51c45281313febdb0f">git repo</a></p>Jake RobersToday during lunch I wrote up a really quick benchmark comparing the performance of class instantiation vs function composition.Light as a Feather: Adding pagination2020-09-17T00:00:00-05:002020-09-17T00:00:00-05:00https://www.jakerobers.com/light-as-a-feather-pagination<p>This will likely be the last article in the series of “Light as a Feather”.
I hope you’ve enjoyed it so far!
Let’s talk a little about pagination.</p>
<p>With CSS as small as possible and Google Analytics removed, the fruit isn’t so low hanging anymore.
One petty annoyance that I noticed with the homepage is that every single post was rendered.
Is it necessary to list all of the publications?
It seems that a fast initial load would have preferrable UX over rendering (a growing number of) all posts.
Furthermore, a user that regularly reads this site likely be most interested in the newest content.</p>
<p>A quick experiment shows that:</p>
<ul>
<li>Loading all of the posts (with descriptions) on the homepage requests 27.05 KB. This means that the page size will increase linearly with each new post.</li>
<li>Loading a paginated 7 posts on the homepage is 8.25 KB. This also ensures that the payload size remains <em>constant</em>.</li>
</ul>
<p>This lightens the load of the html payload on the homepage by 18.8 KB (~70%).</p>
<p>The plan is to keep pagination and wait for <a href="/light-as-a-feather-removing-google-analytics">some analytics</a> (i.e. how often a user navigates to page 2 from the homepage).
This should help drive my decision further.
All in all, keeping pagination feels like a no-brainer.</p>
<p>If you’re interested in implementing pagination to your blog, have a look into <a href="https://github.com/sverrirs/jekyll-paginate-v2">jekyll-paginate-v2</a>.
I had troubles getting the original jekyll-paginate gem running on the latest version of jekyll.
The one drawback to jekyll-paginate-v2 is that it appears to not be supported by GitHub Pages, so that may be a deal breaker depending on your situation.</p>
<p>The Jekyll configuration for my pagination setup is:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pagination:
enabled: true
debug: false
collection: 'posts'
per_page: 7
permalink: '/page/:num/'
title: ':title - page :num'
limit: 0
sort_field: 'date'
sort_reverse: true
category: 'software'
tag: ''
locale: ''
trail:
before: 2
after: 2
</code></pre></div></div>
<h2 id="miscellaneous-improvements">Miscellaneous Improvements</h2>
<p>One other performance improvement that probably does not warrant its own post: I reduced the profile picture on the homepage.
The original photo was around 13 KB, which seemed a bit large.
I was able to take a couple of KB off of this total without any noticable degredations.</p>
<p>Going further, I am experimenting with <a href="https://instant.page/">instant.page</a>.
This will make the blog feel more like a client-side app in regards to navigation.
The only difference is that I have <em>both</em> an extremely fast initial load <em>as well as</em> subsequent navigation.
Who needs JavaScript routing? ;)</p>Jake RobersThis will likely be the last article in the series of “Light as a Feather”. I hope you’ve enjoyed it so far! Let’s talk a little about pagination.Light as a Feather: Removing Google Analytics2020-09-06T00:00:00-05:002020-09-06T00:00:00-05:00https://www.jakerobers.com/light-as-a-feather-removing-google-analytics<p>Gathering analytics data is becoming increasingly difficult for publishers.
<a href="https://ublock.org/">uBlock</a> is commonly used by the average internet user and <a href="https://pi-hole.net/">Pi-hole</a> is quickly growing in the technologist space.
I applaud these privacy-concerned folks for taking personal agency in a time where internet privacy is more difficult than ever.
From a publishers point of view, users ghosting analytics is simply a case of “rolling with the punches”.</p>
<p>While I had Google Analytics on my site for about two years, I seldomly checked it.
It felt more like giving Google free data more than anything.
Part of the issue is that I never really took the time to learn the ins-and-outs of the Google Analytics tooling.
Likewise, I also did not engage in marketing campaigns where this feedback could be vital.</p>
<p>What are my “business” requirements for analytics on this blog?
Not much.
It’s a personal site tucked really far back in the corners of the internet.
Since nothing is monetized, engagement is not something that’s really on the todo list.</p>
<p>With this in mind, it seems excessive that the Google Analytics tracker constituted approximately <em>75%</em> of the page-weight.
In this case, I’ve decided that it is better to drop Google Analytics – constituting a giant step towards my philosophy of “only give the users what they ask for.”</p>
<p>So what now?</p>
<p>I’ve decided to exchange Google Analytics for a completely server-side analytics software called <a href="https://www.awstats.org/docs/awstats_setup.html">awstats</a>.
Awstats parses server logs to gather information regarding traffic to the site.
I have these statistics digested daily, and review the results about once per month.
The installation is relatively easy, and reviewing the data is as easy as rsyncing a pdf to my local machine.
Of course, I don’t run the rsync command directly.
It is baked into my <a href="http://localhost:4000/a-simple-tool-memento">daily routine (last paragraph)</a>.</p>
<p>Some of the general information that is reported includes:</p>
<ul>
<li>Number of unique visitors YTD (likely ip-based since there is no cookie tracking).</li>
<li>Total number of visits YTD</li>
<li>Total page views</li>
<li>Hits</li>
<li>Bandwidth used</li>
</ul>
<p>The above statistics are broken down further by:</p>
<ul>
<li>month of year</li>
<li>day for the current month</li>
<li>day of week</li>
<li>hour</li>
<li>IP address/host with last visit</li>
<li>robots</li>
</ul>
<p>Other sections include:</p>
<ul>
<li>Most viewed pages</li>
<li>Operating system</li>
<li>Browser</li>
<li>Origin: direct hits, links from search engines, and links from external pages (i.e. Hacker News)</li>
<li>Unknown user agents</li>
</ul>
<p>While going through some of this data, I learned that the most frequent unknown user agents that visits this site is <code class="language-plaintext highlighter-rouge">Blackboard_Safeassign</code>. Interesting that my site is archived by Blackboard and used in conjunction with their plagiarism detection software.</p>
<p>One other interesting discovery is that 80.2% of my traffic is from non-mobile devices. Though with 17.7% being Linux, I’m sure a good chunk of the desktop traffic is just from me. However, it is also very possible that some bots are spoofing their operating system.</p>
<p>If you’re not concerned about client exclusive information, I implore you to look into server-side analytics.
This lightens the JavaScript load on the client and does not donate your data to other providers.
Finally, since server-side analytics are not dependent on the client, so you’re bound to get precise data!</p>
<p>Goodbye JavaScript page-weight, for a feather I will become.</p>Jake RobersGathering analytics data is becoming increasingly difficult for publishers. uBlock is commonly used by the average internet user and Pi-hole is quickly growing in the technologist space. I applaud these privacy-concerned folks for taking personal agency in a time where internet privacy is more difficult than ever. From a publishers point of view, users ghosting analytics is simply a case of “rolling with the punches”.Light as a Feather: Improving CSS Page Weight2020-08-31T00:00:00-05:002020-08-31T00:00:00-05:00https://www.jakerobers.com/light-as-a-feather-css-page-weight<p>Performance is a software cornerstones that tends to be left behind.
It’s easy to get distracted while pushing through countless features.
Let me be clear about something though: Performance <em>is</em> a feature too!
Not only is it a feature, but it also leads to great user experience.
The UX of this blog will hands-down beat the UX of any ad-bloated recipe site.</p>
<p>Web performance falls under two categories:</p>
<ol>
<li>algorithmic complexity</li>
<li><code class="language-plaintext highlighter-rouge">filesize * latency</code></li>
</ol>
<p>Many javascript-heavy applications ought to be concerned about both #1 and #2.
However, since my blog is static, I don’t have to concern myself with algorithmic complexity.
This allows me to focus solely on page weight of the website.
And while I could lower latencies through CDNs, I will put that on the todo list for another day.</p>
<p>For too long I’ve been putting performance of this website on the backburner.
This made sense in the beginning when I just wanted to get something shipped.
Now that there is a good chunk of content out, I can begin refining some of the rough edges.</p>
<p>Let’s first start with one of the things that I did right.
This website used to ship with <a href="https://raw.githubusercontent.com/tachyons-css/tachyons/master/css/tachyons.min.css">Tachyons via CDN</a>.
Why was this a great choice?
I’m glad you asked.
You see, had I forgotten to configure Nginx to gzip CSS responses.
So had I hosted my own version of Tachyons, I would be sending 74kB instead of the gzipped 10kB equivelent.
Plus, most of these styles were completely unused!</p>
<p>With this being said, let’s start with the quick wins.
There’s nothing quicker than gzipping your responses.
We just need to slightly modify the nginx config:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
# ...
gzip on;
gzip_types text/css;
gunzip on;
}
</code></pre></div></div>
<p>Now we have set outselves up for success when we ship our own CSS stylesheet.
This also decreased the size of the homepage by about 7kB.</p>
<p>If you are looking to optimize CSS on your own website, we need to begin with a little philosophy.
The most intelligent person I know in CSS philosophy is Adam Morse.
Go ahead and read his very insightful post <a href="http://mrmrs.cc/writing/scalable-css/">about keeping it DRY</a>.
In fact, all of his blog articles are very high quality and are worth reading.
Scaling CSS is an uphill battle without the mindset of functional, atomic CSS.</p>
<p>Now that you have a background on CSS style reuse, we can jump into removing unused styles.
The last thing you want to do is give somebody a resource that they didn’t ask for.
So instead of shipping the entire Tachyons library to my users, I will need to pluck out only the styles in use.
With the <a href="">blessing of open source</a>, I can utilize <a href="https://purgecss.com/">PurgeCSS</a> and <a href="https://github.com/fmarcia/UglifyCSS">uglifycss</a>.
I am also going to download a copy of Tachyons and replace the CDN.</p>
<p>Uglify will minify the CSS content, while PurgeCSS will remove any unused styles.
The bash command (not gulp, webpack, or brunch!) that I use for generating the CSS is:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>purgecss --css assets/tachyons.css --content _site/**/*.html,_site/*.html \
| ./bin/pluck_css.py \
| uglifycss > assets/styles.min.css
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">pluck_css.py</code> is a quick and dirty script that aggregates all of the css content from the json output of PurgeCSS:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/env python3
#./bin/pluck_css.py
import json, sys, fileinput
content = ''
for line in fileinput.input():
content = content + line
parsed = json.loads(content)
css = ''
for entry in parsed:
css = css + entry['css']
print(css)
</code></pre></div></div>
<p>Now we have a small CSS file that is only what we need!
I can go ahead and replace all html templates to utilize styles.min.css.
Now here is the drum roll…
What could have been 74kB through shipping a non-gzipped Tachyons libary…
is now…</p>
<p><strong>2.1kB! A 97% reduction.</strong></p>
<p>What’s next on the docket for performance improvements?
It looks like Google Analytics is consuming 52.8kB.
This is 70% of the current page weight now.
With this in mind, I plan on either cutting out analytics, or find a lighter solution.</p>Jake RobersPerformance is a software cornerstones that tends to be left behind. It’s easy to get distracted while pushing through countless features. Let me be clear about something though: Performance is a feature too! Not only is it a feature, but it also leads to great user experience. The UX of this blog will hands-down beat the UX of any ad-bloated recipe site.I Deleted Social Media2020-07-18T00:00:00-05:002020-07-18T00:00:00-05:00https://www.jakerobers.com/i-deleted-social-media<p>If I wrote a 140 character tweet, could I persuade you to change your opinion on which you disagree?
If I read a 140 character tweet, could I change my opinion on which I disagree?
If I respond angerly to a 140 character tweet, am I improving myself mentally? morally?
If I see a wall of text on Facebook expressing an opinion, but don’t feel like arguing in the comments, was this beneficial?
This is my criticism of the not-so social media</p>
<p>Social media is crafted by psychologists to keep you engaged.
Extreme engagement fuels tension between groups and pushes toxicity and anger.
It is too common to find an angry post or feel angry after reading a post.
Most arguments are purely emotional, and blockades any chance for real discussion.
Again, what <em>value</em> does this have?</p>
<p>Social media has destroyed all long-form of communication.
How is it that people could attend live debates in the 1800s for upwards of six to eight hours?
Not only were the discussions lengthly, but the content was cognitively demanding.
Nowadays, our attention is so severely fractured.
I am convinced that social media has destroyed our society beyond repair.</p>
<p>Social media is not solely culpable – news as a whole is.
To be crystal clear, this is not an attack on the “Fake News” or other nonsense that people have been shouting.
News had begun to depreciate society since the inception of the Telegram.
If you’re not convinced, but open-minded in hearing why, I recommend reading Neal Postman’s <em>Amusing Ourselves to Death</em>.</p>
<p>Personal betterment starts with holding an attention span greater than 5 minutes.
Engage in long-form discussion with individuals.
Listen to long-form discussions such as podcasts.
Read books, essays, and other thought-provoking media.
Don’t let attention thiefs tell you how to consume information.</p>
<p>I quit social media.
I am not here to tell you how great my life is because I quit.
I am only here to tell you that I do not miss it.
Quitting social media was merely a crossroads on the journey of personal development.
I have now replaced contempt-driven tweets with thought provoking podcast discussions.</p>
<p>I cannot persuade somebody in 140 characters.
I cannot be persuaded by 140 characters.
I cannot improve myself with invoking anger on another.
I cannot benefit from someone else’s anger.</p>
<p>If you vehemently disagree with this notion, you’re not ready to admit the bind that social media has on you.
Maybe you will get there, maybe not.
The materials that helped me kick the habit are:</p>
<ul>
<li><em>Amusing Ourselves to Death</em> by Neil Postman</li>
<li><em>Digital Minimalism</em> by Cal Newport</li>
<li><em>Deep Work</em> by Cal Newport</li>
</ul>
<p>Sure, there may be the fear of missing out, but once you finally see social media for what it is, you will be content with the decision of leaving.
Regardless of how convincing this post is, I can guarantee it is better than the 140 character equivelent.</p>Jake RobersIf I wrote a 140 character tweet, could I persuade you to change your opinion on which you disagree? If I read a 140 character tweet, could I change my opinion on which I disagree? If I respond angerly to a 140 character tweet, am I improving myself mentally? morally? If I see a wall of text on Facebook expressing an opinion, but don’t feel like arguing in the comments, was this beneficial? This is my criticism of the not-so social media