Sunday, September 13, 2020

Your Hiring Process is Broken



Hiring staff is hard. Hiring software developers is particularly challenging. Despite our best efforts, positions are frequently left open for weeks and months. Teams spend countless hours screening resumes, giving interviews, and reviewing coding samples. The process is time consuming and expensive. And here's the tough part to admit: it is practically certain that the person you hired was not the best candidate to come across your desk.

Your hiring process is broken.

The problem is we are chasing unicorns. When I was working with PowerInbox, we had adopted a widely published recruiting process called WHO. The process is built on the premise of finding and hiring "A Players". But I know first hand that the company did not even attract "A Players" let alone hire them. In tech, the top talent go to top tier firms, or here in New York top talent goes to Wall Street. That means other companies are looking for an ideal employee that they will never find and in the process will pass over very talented candidates.

But chasing unicorns is just part of the problem. It is likely that your process involves too many people, and gives too many of them veto power over candidates. I myself am guilty of setting up a hiring process with too many interviewers. The idea is to give the current team ownership on the hire. Unfortunately the more people involved in a hiring decision, the more likely a candidate is rejected. At issue is basic human nature; it is easier to rule someone out than rule someone in.

There is a conventional thinking that fuels these practices. The thinking is that it is very expensive and disruptive to make a "bad hire" and therefore every effort should be made to prevent bad hires. While I would never suggest that companies hire unqualified candidates, it is apparent that the typical hiring process goes well beyond the point of marginal return. In fact, I will put forth that many "bad hires" weren't bad hires at all, but instead are examples of bad management.

It's not hopeless. You can fix this.

Empower your hiring manager. Hiring is not a task suited for committees. And yet that is how most companies hire people. When I interviewed with Amazon, I spoke with a dozen of their team. In my experience it is common for candidates to speak with at least six individuals. It is very difficult for large hiring teams to come to consensus on a given candidate, especially when there are many choices. It is common for "analysis paralysis" to result. Interview by committee is also very taxing on both the company and the candidates.

Set a limit on resumes. Be realistic. If you receive one hundred applications for a senior software engineer, what is the probability that one of those candidates will be a productive employee for you. It's a near certainty, and yet we are tempted to keep screening resumes because someone else could be better. Someone else may be better, but we will never know. You will never know how any of the candidates you pass over will compare to the person you hired. So you must take that fear out of the equation. Instead of leaving a position open until the right person comes along, set a fixed size of the candidate pool that you will consider and stop accepting applications when that number is reached.

Narrow your finalists based on objective skills. A typical hiring process starts with a recruiter spending a half hour on the phone with candidates that look good on paper. If you limited your resumes to 100 you will likely want to have screening calls with at least the top twenty prospects. That's ten hours spent on the phone, and nearly as much time to summarize and rank the candidates. And it's not time well spent. Instead, test the individuals on the key skills first. The test should be difficult enough to truly separate the skilled from the un-skilled.

Here's the advantage of testing first: you will have a high degree of confidence in every candidate you speak with. Take the top ten and schedule a limited set of conversations with each of them (as described in the next paragraph).

Limit the number of conversations. You would think that the more time spent with the candidate, by more people, would supply better information. In the book Talking to Strangers Malcom Galdwell highlights research that demonstrates more time does not equal better judgement. On the contrary, more time with the candidate will often result in worse decisions. My suggestion is to hold no more than four conversations. One with the recruiter. One with the hiring manager. One with a peer, and one with a subordinate. If the job is not managerial, then hold two peer conversations.

Run a "Democratic Lottery". The democratic lottery is another concept borrowed from the research of Malcom Gladwell (and described here). For your process, let the hiring manager narrow the pool down to three or four candidates and then randomly pick one. That's right. Randomly select the finalist.

While a lottery is not a perfect system, it has advantages over the typical hiring process. First, employees selected by lottery would be more representative of the population as a whole, resulting in a diverse workforce. Second, you are unlikely to pass-over highly qualified candidates for those that "feel right". Finally, your process is faster, less expensive, and far less stressful on the organization.

So here it is. Review a specific number of resumes. Test their skills first. Hold a small number conversations with your top ten. Have the hiring manager pick three or four. Randomly select the finalist. I assure you that your hiring will be faster and your new hires as good or better than before.

 

 

Thursday, May 28, 2020

Using React Hooks with Firebase Authentication


If you are a React developer, and unless you have been living under a rock for the past year, you will be familiar with Context, Reducers, and Hooks. Sure, these are usually just called "Hooks" or "custom hooks". Hooks give the developer two advantages over the old-school method of class-based components. These are:

  • Ability to build an entire application using function components, and
  • Elimination of the need for Redux.

Functional components make the code more concise and readable. They also provide a fairly significant performance boost over classes. As for Redux. Well. Good riddance.

Armed with hooks I decided that my next React project would be written entirely with functions and hooks. And this approach worked great until I needed to wire up Firebase Authentication. I discovered that the tutorials available provided examples using class-based components. This article then, is my attempt to fill that gap with my notes on using Hooks with Firebase Authentication.

Here are the things I had to build.

  • A "context" with a "context provider",
  • A "reducer",
  • A custom hook that uses an Effect hook,
  • Helper functions to interact with Firebase, and
  • All the JSX to make it work.

The Reducer

Let's start by writing a Reducer. That might seem a bit backward as I typically begin with my Context. But since the Reducer is referenced by the Context, I'll start with the Reducer. The reducer is used to make changes to the state of the properties managed by the Context. If all this sounds like jargon (which it is), then I suggest reading up on React Hooks; especially useContext, useReducer, and useEffect. I'd love to explain all that here, but well, this article will be very long as it is.

The Reducer and the Context will be supporting Firebase Authentication. I should mention here that I am using the Authentication SDK and not the drop-in UI. I should also mention that I am only describing email based authentication. The properties of a "user" in Firebase are:

  • Name, which is the "display name" of the user. The display name is usually the user's full name.
  • Email, an email address the user is treating as her id.
  • PhotoURL, a URL to an image the user has uploaded for their avatar.
  • EmailVerified, a boolean value that indicates the user responded to a verification message.
  • Uid, which is a global unique identifier assigned by Firebase to the user.

Reducers contain logic for adding, removing, or manipulating the Context's data. In this case though, the user data is managed by Firebase via its' SDK. All our reducer needs to do is assure that the context is current. I created a file named SessionReducer.js and included the code below.

    export const SessionReducer = (state, action) => {
        switch (action.type) {
          case "UPDATE":
            return {
              name: action.session.name,
              email: action.session.email,
              photourl: action.session.photourl,
              emailVerified: action.session.emailVerified,
              uid: action.session.uid
            };
          default:
            return state;
        }
      };

The action.type property is part of the Reducer specification. So the Reducer is passed the state and an action object and returns the new value of the state. In this case, we will only take one action that I have set to "UPDATE". The value UPDATE, by-the-way, is a discresionary name set by the developer. Also note that I do not need any import statements here.

The Context

The Context is a bit bigger and contains a couple of functions. I'll show the code and then describe it. I created a file named SessionContext.js and included the code below.

    // React imports
    import React, { createContext, useReducer, useContext, useEffect } from "react";
    
    // Firebase imports
    import firebase from "../firebase";
    
    // My imports
    import { SessionReducer } from "../reducers/SessionReducer";
    
    // initial state values
    const initialState = {
      name: null,
      email: null,
      photourl: null,
      emailVerified: false,
      uid: null
    };
    
    // create the context
    export const SessionContext = createContext();
    
    // create the context provider
    const SessionContextProvider = props => {
      const [session, dispatch] = useReducer(SessionReducer, initialState);
      return (
        
          {props.children}
        
      );
    };
    
    // create the custom hook
    export const useSession = () => {
      const contextState = useContext(SessionContext);
      const { dispatch } = contextState;
      useEffect(() => {
        firebase.auth().onAuthStateChanged(user => {
          var currentUser = {};
          if (user) {
            currentUser = {
              name: user.displayName,
              email: user.email,
              photourl: user.photoURL,
              emailVerified: user.emailVerified,
              uid: user.uid
            };
          } else {
            currentUser = initialState;
          }
          dispatch({
            type: "UPDATE",
            session: currentUser
          });
        });
      }, [dispatch]);
      return contextState;
    };
    
    export default SessionContextProvider;    

Notice near the top of the code I have included an import of the Reducer shown earlier. I have not described the structure of my application but suffice to say that my Contexts and Reducers each reside in folders made for that purpose. You can organize your source files any way you like.

Following the imports, I create and export the SessionContext using React's API for that purpose. It's one line of code that requires no parameters. Next I have a few lines of code to create the "Context Provider". This is a small amount of code that includes JSX that ties the context to components in the application. React has two methods for this: Provider and Consumer, but since I am using functional components exclusively I must use the Provider method.

The Provider binds the Reducer to the Context. In this code I use the useReducer function to deconstruct its' return value into the "session" and the "dispatch". The session is the current state and the dispatch is the function I supplied when I wrote the Reducer. These values are passed as props (via HTML attributes) to the provider context. The {props.children} value assures that this context provider will be available to all children components to the component where it is used. I should also mention that the provider context is just another component. In my case, the context provider is my default exported function.

The Custom Hook

The last function creates the custom hook. By convention the names of hooks start with "use" in lower case; i.e. useContext, useReducer, or in my case useSession. The custom hook first retrieves data from this context, which is the state and the dispatch function, and stores it in contextState. The dispatch function is deconstructed out of the contextState. For my purpose here, I will not need the actual values of the state.

The work of this custom hook is accomplished within a useEffect function. If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate , and componentWillUnmount combined. In this case, whenever a component that uses our hook is rendered, a call is made to the Firebase onAuthStateChanged method. That method is passed a function that checks the state of the current Firebase user. If the user exists then the Firebase used is passed to the dispatch function, otherwise the initial state of the user (which is null) is passed to dispatch.

Wiring it up

The rest is simply writing the custom hook into the app. In this case I wanted to make the hook available to the entire application. To accomplish this I added my context provider to the app.js main code file.

    // React imports
    import React from "react";
    import { BrowserRouter, Switch, Route } from "react-router-dom";
    
    // Material UI imports
    import { MuiThemeProvider, useTheme } from "@material-ui/core/styles";
    import CssBaseline from "@material-ui/core/CssBaseline";
    import Box from "@material-ui/core/Box";
    import Container from "@material-ui/core/Container";
    
    // My imports
    import Dashboard from "./components/dashboard/Dashboard";
    import AppHeader from "./components/layout/AppHeader";
    import HomeDetail from "./components/houses/HomeDetail";
    import SignIn from "./components/auth/SignIn";
    import SignUp from "./components/auth/SignUp";
    import HouseContextProvider from "./contexts/HouseContext";
    import SessionContextProvider from "./contexts/SessionContext";
    import LaunchPage from "./components/dashboard/LaunchPage";
    
    const App = () => {
      const theme = useTheme();
    
      return (
        <BrowserRouter>
          <SessionContextProvider>
            <MuiThemeProvider theme={theme}>
              <HouseContextProvider>
                <CssBaseline />
                <AppHeader />
                <Container maxWidth="xl">
                  <Box m={3}>
                    <Switch>
                      <Route path="/" exact component={LaunchPage} />
                      <Route path="/dashboard" component={Dashboard} />
                      <Route path="/homes/:id" component={HomeDetail} />
                      <Route path="/signin" component={SignIn} />
                      <Route path="/signup" component={SignUp} />
                    </Switch>
                  </Box>
                </Container>
              </HouseContextProvider>
            </MuiThemeProvider>
          </SessionContextProvider>
        </BrowserRouter>
      );
    };
    
    export default App;

In this file there are just a couple of lines to point out. First is the import of the SessionContextProvider. And the others are the JSX SessionContextProvider tags.

To signup a new user bind the function below to your form submission handler.

...
import { signup } from "../../services/firebaseAuth";
...
// form actions
const handleSubmit = e => {
  e.preventDefault();
  enroll();
};

// firebase signup function
const enroll = () => {
  const results = validate(
    {
      email: email,
      password: password,
      firstName: firstName,
      lastName: lastName,
      confirmedPwd: confirmedPwd
    },
    {
      email: constraints.email,
      password: constraints.password,
      firstName: constraints.firstName,
      lastName: constraints.lastName,
      confirmedPwd: constraints.confirmedPwd
    }
  );

  if (results) {
    setErrors(results);
  } else {
    setErrors(null);
    signup(email, password);
  }
};

A couple of points about the code above. First, the validate function it its' constraints are out of the scope of this article. If anyone reads this, and someone asks about validate then I will consider writing another post to describe that function. And second, there is a helper function signup that I show a bit later.

Signing in is similar.

...
import { SessionContext, useSession } from "../../contexts/SessionContext";
import { signin } from "../../services/firebaseAuth";
    
const SignIn = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState();
const { session } = useSession(SessionContext);
const handleSubmit = e => {
  e.preventDefault();
  authenticate();
};

// firebase signin function
const authenticate = () => {
const results = validate(
    {
      email: email,
      password: password
    },
    {
      email: constraints.email,
      password: constraints.password
    }
  );
 
  if (results) {
    setErrors(results);
  } else {
    setErrors(null);
    signin(email, password);
  }
};
...

Again, the signin function is shown later. And lastly I sign out with a link button on my AppBar that executes the signout fuction (and yes, the function must be imported).

    ...
    <button color="inherit" component="{Link}" onclick="{signout}" to="/">
        Sign Out
    </button>
    ...

Firebase

The final pieces of the puzzle are the supporting functions that call out to Firebase. These are the signup, signin, and signout functions mentioned above. I organized the functions into a single source code file. For purposes of this paper, these functions were copied directly from the Firebase documentation website and do not incude any of my application specific code. I created the file firebaseAuth.js for this. The code is below.

    // Firebase imports
    import firebase from "../firebase";
    
    export const signup = (email, password) => {
      firebase
        .auth()
        .createUserWithEmailAndPassword(email, password)
        .catch(error => {
          // Handle Errors here.
          alert("Error during sign up " + error.message); // delete this!
          var errorCode = error.code;
          var errorMessage = error.message;
          if (errorCode === "auth/weak-password") {
            alert("The password is too weak.");
          } else {
            alert(errorMessage);
          }
          console.log(error);
        });
    };
    
    export const signin = (email, password) => {
      firebase
        .auth()
        .signInWithEmailAndPassword(email, password)
        .catch(function(error) {
          // Handle Errors here.
          var errorCode = error.code;
          var errorMessage = error.message;
          if (errorCode === "auth/wrong-password") {
            alert("Wrong password.");
          } else {
            alert(errorMessage);
          }
          console.log(error);
        });
    };
    
    export const signout = () => {
      firebase
        .auth()
        .signOut()
        .then(function() {
          // Sign-out successful.
        })
        .catch(function(error) {
          console.log(error);
        });
    };

So the next person who needs to implement Firebase Authentication with React Hooks now has some reference material to start with. Questions and comments are welcome.

You might also like ...