What I've Learned Transitioning to Private Sector as a Software Engineer
Tags: webdev, javascript, beginners, programming
Published: 2024-03-15 08:29:14

After years working for the government, I made the move to the private sector. This career transition gave me a chance to seriously reflect on development practices and approaches. Here are some of the biggest lessons I realized:

**Development Best Practices**

Following good coding habits is crucial for keeping codebases healthy and extensible long-term. Nowadays, I prioritize:

- Using TypeScript’s type system properly instead of excessive type castings like `as`. Using type guards provides stronger type safety:

```typescript
interface User {
  name: string 
  email?: string
}

const validateEmail = (user: User) => {
  if ("email" in user) {
    // Type guard checks for optional 'email' prop
    console.log(user.email.toUpperCase())
  }
}
```

- Checking for null/undefined explicitly with optional chaining instead of relying on non-null assertion operators like `!`:

```typescript
const userName = user?.name ?? "Anon"
// Optional chaining handles null/undefined
```

- Being smart about utility libraries like Lodash. They can make code more readable for complex operations, but watch out for bundle bloat.

I’ve also become way more aware of areas that create technical debt:

- Avoiding nested data structures in databases, as they hurt queryability and make it harder to evolve data models cleanly.
- Preventing tight coupling between application logic and the data model to reduce cascading changes when models update.
- Establishing consistent code styles and patterns across the team for better readability and knowledge sharing.

Incorporating automated tests is another big realization, but with a pragmatic approach. Unit, integration, and end-to-end tests are all valuable, but I prioritize what really needs comprehensive test coverage.

**Backend Architecture and TypeScript**

On the backend, I’ve seen great benefits from TypeScript’s static type checking — especially with data transfer objects (DTOs). Defining DTOs with strict types upfront helps catch formatting issues early in requests/responses:

```typescript
// Data Transfer Object 
interface CreateUserDTO {
  name: string
  email: string
  password: string
}

// Type-checked request body
const createUser = (req: Request<{}, {}, CreateUserDTO>) => {...}
```

That said, TypeScript has limits. Its type system mainly enforces compile-time safety, so extra runtime validation is still smart for handling sketchy or unpredictable data.

I’ve also solidified my preference for separating concerns on the server-side:

- Using controllers strictly for routing/auth logic
- Separate service layer for core business logic
- Repository layer for database access

```typescript
// Controller
export const getUserController = async (req: Request, res: Response) => {
  const user = await getUserService(req.params.id)
  res.json(user)
}

// Service
export const getUserService = async (id: string) => {
  const user = await userRepository.findById(id)
  // ...business logic
  return user
}

// Repository 
export const userRepository = {
  findById: (id: string) => { /* DB query */ }
}
```

**Databases**

My experience has only reinforced that SQL’s tabular relational model is better than NoSQL’s nested data approach for most applications.

While NoSQL’s flexibility to evolve schemas easily is appealing early on, those advantages get outweighed by drawbacks around query efficiency and performance at scale. Normalized data models, leveraging database relationships and SQL’s powerful querying, provide a sturdier long-term foundation.

```json
{
  "_id": "pr_001",
  "name": "Product 1", 
  "categories": ["Electronics", "Computers"],
  "specs": {
    "weight": 2.5,
    "dimensions": {
      "length": 12,
      "width": 8,
      "height": 3
    }
  },
  "variants": [
    { "id": "pr_001_red", "color": "red" },
    { "id": "pr_001_blu", "color": "blue" }
  ]
}
```

While this flexible schema is convenient during initial development, it becomes increasingly problematic over time:

- Querying across products and variants requires complex join-like operations
- Updating nested specs or adding new variant properties is cumbersome
- Aggregating and analyzing catalog data is inefficient

In a normalized SQL schema, the same product data could be split into separate tables like:

```
Products (id, name, weight)
Categories (id, name)
Product_Categories (product_id, category_id)
Specs (product_id, length, width, height)
Variants (id, product_id, color)
```

This separation allows queries like “Get all products under 3 lbs with variant colors” to be executed efficiently using JOINs. New properties can be added seamlessly, and analyzing the catalog data is much simpler with SQL’s advanced querying capabilities.

So while NoSQL’s flexible schema provides early convenience, the long-term sustainability and performance advantages of SQL’s relational model become increasingly evident over time.

**Frontend Development**

For frontend architecture, I’ve looked at the tradeoffs between micro-frontends and monolithic frontends:

**Micro-Frontends**

- Independent deployment
- Gradual framework updates
- Limit regression risks
- But increases complexity

**Monolithic Frontend**

- Simpler, fewer duplicates
- Easier to bundle/deploy
- Makes sense for low-complexity
- Can inhibit large-scale evolution

I’ve also got tons of experience with both Vue.js and React recently. While React’s huge ecosystem and adoption are clear pros, I prefer Vue’s coherent reactivity model and separation of concerns between script, template, and styles:

```html
<template>
  <div>{{ upperCaseName }}</div>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  name: String
})

const upperCaseName = computed(() => props.name.toUpperCase())
</script>
```

In my experience, Vue provides an easier entry point for rapid development, while its opinionated state management, watchers, and computed properties help avoid subtle bugs. With TypeScript, React’s hooks paradigm can get overly complex compared to Vue’s more straightforward reactivity.

**Project Management**

From an execution standpoint, I’ve gained a greater appreciation for diligent ticket grooming and documentation. Well-defined tickets capturing clear requirements, repro steps, and user narratives promote shared understanding and productive collaboration.

**Bug Ticket Template:**

🐞 Summary Description:

Profile Image Upload Failing

Description:

When attempting to upload a new profile image from the Settings page, the upload fails with an error alert.

👣 Steps to Reproduce:

1. Go to Profile Settings page
2. Click “Upload New Image”
3. Select an image file
4. Click “Save Changes”

🚫 Actual Result:

Error alert “Upload Failed” is displayed and existing profile image remains.

✅ Expected Result:

New image is displayed and replaces the existing profile image.

**Task Ticket Template:**

📖 Description:

In order to improve product page experience 🎯, as a front-end developer 👨‍💻, I need to add an image zoom feature when clicking product images ✅.

✅ Acceptance Criteria:

- User can click thumbnail to zoom
- Overlay displays zoomed image
- User can move and resize overlay
- Overlay hides when clicking off

📋 Other Information

Attach Figma designs for zoom overlay styles/interactions.

Breaking down larger efforts into granular sub-tasks with estimations helps manage stakeholder expectations and workloads. Working off vague or thinly-documented tickets inevitably leads to misalignment and rework later.
I've also refined my perspective on strategic *debugging workflows*. While IDE debuggers with breakpoints are powerful, well-placed console.log()statements remain invaluable - especially for analyzing production issues or reasoning about async behavior.

**Looking Ahead**

Since transitioning to the private sector, all these lessons around best practices, architectural patterns, technology tradeoffs, and processes have crystallized for me. But I recognize this industry moves rapidly, so any rigid dogma will become obsolete quick.
I aim to keep an open, growth mindset - continuously re-evaluating approaches while pragmatically applying techniques that fit the current context best. This intentional reflection period has reinvigorated my passion while giving me a refined, balanced perspective on full-stack development.