Why Git Works Where TFVC Doesn't

We’re in the process of moving to git right now, and one of the main questions I get is how will this ACTUALLY be any better than TFVC. (The other question is how much longer before we’re fully moved to git? Which warrants its own piece.)

See, we’ve tried “branching” in TFVC, and it only caused us more pain. So there is some legitimacy to this question.

We were doing some hard core bastardized scrum, and had successfully split our department into scrum teams. Who would work on all kinds of stuff each sprint. Maybe half the team was working on a global refactoring of a pattern, maybe the other was working on migrating some key workflows from our old static classes to new workflows leveraging dependency injection. Either way, different work with different timeframes.

Because we’re agile, we need to be able to work on lots of different things at any given time. But branching in TFVC is different than it is in git. A branch in TFVC is immediately available to everybody to see. And it never really goes away, but you can hide it (cool!). But you can’t use that name again (bummer).

Since it never goes away, and we didn’t want to be constantly reminded of our bad branch naming, we picked a very generic naming scheme. Git branching would say be specific with you’re development branch names. But ours were Work-1, Work-2, .etc. We had about 10 teams at that point, and we ended up with 32 “work” branches.

Ok, 32, not too bad. But it was. 32 is not enough for the number of features & bug fixes being developed by 10 (and growing) teams. We probably needed 100-500 to have actually been successful. But with generic names like work-2, it’s hard to keep track of ownership. So we stopped at 32.

And this is where it got messy.

Scenario: The team is working on three different things in Work-14. One is done, while two are in progress. That whole branch has to be brought in now. Because that one completed feature is made up of 100 different changesets over six weeks. So we can’t just cherry pick that one feature. And now we’ve brought in two half-done features into mainline. We’re constantly testing mainline, so we feel OK about this, but the reality is that it’s not mainline that causes the problem. Another team just got two half done projects dumped into their branch, because they rebased from mainline. And not just one other team, but nine. And then they’re all propagating that back out to everybody else.

In the end, the chaos comes from the unfinished work being forced into everybody’s “clean” branches. But we’re actually paying extra for that chaos, because we now have to constantly rebase and merge branches, which takes time and understanding of a myriad of incongruent changes.

Earlier I said we’ve “tried” branching. We did. And then we abandoned it. We still have the chaos, but not the extra cost. Everybody commits to mainline, it is constantly under test, and we ship often. Builds break, and it affects everybody, but it gets resolved quickly.

I’m of the belief that is has made our developers better, knowing the impact of a bad commit. But it isn’t easy.

So we’re switching to git. And we can have as many branches as we want. And they can be local. And they can have good names. And they can be deleted. And we can still commit to mainline if we want to.


How Yield Works

This code should work:

IEnumerable<T> ShouldWork<T>(T  obj)
{    
    if (!obj is IEnumerable<T>)
    {
        yield return obj;
    }
    return (IEnumerable<T>) obj; 
}

But it doesn’t.

I thought it would work. I like using yield to take full advantage of delayed enumeration, rather than creating new lists or arrays all the time.

Everything looks right; the return types are good. Except it won’t compile. You get a nice message about having to use yield inside an iterator when trying to return (IEnumerabl<T>)obj directly.

Don’t try yield return (IEnumerable<T>)obj. That will do exactly what you’d think. It won’t compile either, because now the return types don’t match, because yield return (IENumerable<T>)obj needs a method return type of IEnumerable<IEnumerable<T>>.

So what to do?

Obviously, try yield break. Because nothing else is making sense at this point. But of course, that’s not what yield break is for in the least, so it doesn’t work. Yay, not completely crazy.

This issue didn’t come up for me until I was trying to combine recursion & LINQ-expressions. And was stumped. Googling the motivator (LINQ and recursion) yielded (pun intended) no valuable results.

I grabbed the nearest .NET nerd of my colleagues, and went straight to the whiteboard. In doing so, I realized that recursion has nothing to do with this not compiling (still not crazy!). On the other hand I still had to convince my colleague that the code wouldn’t compile. (He went thru literally the same steps I did. Again, not crazy).

So we seem to have found a cool nugget in the compiler, that seems to say, “if you want to yield return once in a method, you must yield all other returns in that method.” OK. That actually kind of makes sense. Delayed enumeration would mean that the compiler is wanting to deal w/ things granularly.

My (sad hack of a) solution: Fake the yield.

IEnumerable<T> DoesActuallyWork<T>(T  obj)
{ 
    if (!obj is IEnumerable<T>)
    {
        return FakeYield(obj);
    }
    return (IEnumerable<T>) obj; 
}

IEnumerable<T> FakeYield<T>(T obj)
{
    yield return obj.
}

All that to avoid explicitly instantiating lists.


Creating a vs Extension

I’m a moderate user of extensions. As in, I have some favorites, but I’m not trolling the gallery everyday.

I’ve never made one before. No idea has either been both big enough and small enough to warrant me building an extension.

In April 2016, at Build, I saw Mads Kristensen create & publish (with integrated CI, and pretty images) a brand new extension during a 1 hour session. It was a great talk, and I had promised myself that I would watch it again on Channel9. I also signed up to be a VS Partner while at build.

I received an email about a week ago saying my VS Partner status would be canceled, unless I published a product in the next week.

Two days later, I came across a fitting challenge for an extension. So I set out to build it.

I followed Mads’ video, and it was surprisingly easy. The docs are still very confusing for the VS interfaces, but it was still doable.In the end, what took Mads less than an hour on stage, took me about 5 hours of total work at my desk.

All in all, it was still surprisingly easy, and I regret not doing it sooner.


VBScript Conditional Includes

This is a nasty one to track down in production, so I’m going to show you here.

VBScript has a fun way of interpreting include references that are nested inside an if statement. It will load all include references. If you have different functions, with the same name, it will use the second one.

Example

myTest_inc1.asp:

<%
Response.Write "<br/>Function 1 loaded...<br/>"

function myFunc(int1, int2)
    Response.Write "<br/>Function 1 executing...<br/>"
    myFunc = int1 * int2
end function 
%>

myTest_inc2.asp:

<%
Response.Write "<br/>Function 2 loaded...<br/>"

function myFunc(int1, int2)
    Response.Write "<br/>Function 2 executing...<br/>"
    myFunc = int1 + int2
end function
%>

myTest.asp:

<%
    if (true) then
        Response.Write "<br/>Case 1 executing...<br/>"
%>

<!-- #include file="myTest_inc1.asp" -->

<% 
    else
        Response.Write "<br/>Case 2 executing...<br/>"
%>

<!-- #include file="myTest_inc2.asp" -->

<%
    end if
    
    Response.Write myFunc(3,3)
%>

The output of myTest.asp is:

Case 1 executing…

Function 1 loaded…

Function 2 executing…

6

Thanks for reading.