2015년 6월 29일 월요일

개발자에게 부탁하라.

 zdnet Korea에서 꾸준히 보고 있는 컬럼, 특히 임백준씨의 글은 공감가는 내용이 많다.
 그 중에 최근에 올라온 컬럼 중 하나인 "개발자에게 부탁하라"는 최근의 나의 상황이 많이 반영된 탓인지 어느 글보다 더 공감이 되는 내용이다.

"개발자에게 부탁하라(전문)"

 신문사들이 위기를 겪고 있다. 신문사로 향하는 트래픽이 현저히 줄어들고 있기 때문이다. 여기에서 트래픽은 웹사이트나 콘텐츠를 이용하는 사람들의 수를 의미한다. 한국에서 발생하는 인터넷 트래픽은 네이버를 정점으로 하는 소수의 포털 사이트가 독차지한지 오래되었다. 신문사들이 설 땅은 별로 없다.

 트래픽의 흐름을 살펴보면 전통적인 언론사들이 힘을 잃는 양상이 그대로 드러난다. 조선, 중앙, 동아, 한겨레, 경향과 같은 신문사는 자신의 힘으로 트래픽을 발생시키지 못한다. 그들이 생산하는 기사나 콘텐츠가 대부분 포털을 통해서 소비되기 때문이다. 콘텐츠는 신문사가 생산하지만 트래픽은 포털로 몰린다. 과거에 신문 헤드라인이 수행하던 기능을 이제는 실시간 검색어가 대신하고 있다.

 랭키 닷컴에 따르면 2011년 11월에는 트래픽 상위 10등 안에 2개의 신문사가 포함되어 있었고, 상위 30등 안에는 6개가 포함되어 있었다. 하지만 4년 후인 2015년 5월에는 상위 10등 안에 단 1개의 신문사도 없고, 경제지를 제외하면 30등 안에 2개의 신문사가 포함되어 있을 뿐이다. 가장 순위가 높은 조선닷컴이 16~19등이다.

 자본주의 사회에서 트래픽은 광고와 직결된다. 풍부한 트래픽은 높은 광고수입을 의미하고, 빈곤한 트래픽은 낮은 광고수입을 의미한다. 그래서 트래픽은 돈이다. 종이 신문을 보는 사람이 거의 없고, 신문사 사이트를 방문하는 사람도 별로 없는 요즘에는 신문사들이 광고수입을 올리기 어렵다. 신문사에 광고를 주는 기업은 실제로 광고효과를 기대해서가 아니라 만약의 경우를 위한 보험의 의미로 광고를 줄 뿐이다.

 다른 나라도 마찬가지다. 미국에서는 100년이 넘는 유수한 역사를 자랑하던 신문사들이 속속 문을 닫고 있다. 신문사가 문을 닫았다는 뉴스는 이제 뉴스가 되지 않는다. 유력지의 하나인 워싱턴 포스트가 아마존에게 팔린 것은 힘의 방향이 어디를 향하고 있는지 보여주는 사례다. 뉴욕타임스는 직원 수를 줄이며 회사 전체를 인터넷과 모바일 중심으로 재편하기 위해 노력하고 있다.

 갈수록 힘을 잃는 우리나라의 신문사들은 문제를 해결할 방법을 찾지 못하고 있는 것처럼 보인다. 해외에 있는 소프트웨어 회사에게 외주를 맡기기도 하고, 모바일 중심의 전략을 강조하기도 하지만 개편의 수준은 홈페이지 레이아웃의 재편성 수준을 벗어나지 못한다. 모든 신문사의 속사정을 알지 못하기 때문에 단언하기 어렵지만, 적어도 내가 관찰한 바에 의하면 그렇다.

 이유는 간단하다. IT를 비용으로 보는 시각을 바꾸지 못하기 때문이다. 컴퓨터 프로그래밍을 그 자체로 비즈니스로 생각하지 않고 단순한 도구로 바라보기 때문이다. 기자는 독자를 위해서 일하고, 프로그래머는 그런 기자들을 위해서 PC를 고치는 사람 정도로 생각한다. 신문사를 특정해서 이야기하고 있지만 사실은 모든 업계에 공통적인 이야기다. 인터넷, 디지털, 모바일, 소프트웨어 시대에서 주인공은 개발자다. 당연한 일이다. 주인공에게 단역을 맡겨놓고 작품을 만들려고 하니 작품이 나오지 않는 것이다.

 한국에 있는 수많은 회사들이 안고 있는 문제의 하나는 PC와 프린터를 고치고 LAN을 연결하는 일을 소프트웨어 개발과 혼동한다는 점이다. 그런 일을 폄하하는 것이 아니다. PC를 수리하는 일과 소프트웨어를 개발하는 일이 타자기를 수리하는 일과 타자기를 쳐서 기사를 작성하는 일만큼이나 다르다는 사실을 말하는 것이다.

 신문사가 인터넷과 모바일을 중심으로 자신을 재편하고 트래픽을 끌어 모으고 싶으면, 개발자에게 부탁해야 한다. 기술수준이 높은 개발자를 고용해서 팀을 구축하고, 그들이 회사 내부에서 마음껏 일을 저지르도록 장려해야 한다. 기자들과 시간을 보내면서 신문사의 생리를 파악하도록 하고, 편집과 뉴스전송의 원리를 이해하도록 하고, 신문이라는 산업 전체의 틀을 볼 수 있도록 도와주어야 한다.

 그들이 원하는 컴퓨터와 소프트웨어 도구를 구입해주고, 해외에서 열리는 개발자 컨퍼런스에 참석시키고, 책과 자료를 구입하도록 지원해주어야 한다. 업계의 다른 개발자들을 초청해서 세미나를 열도록 하고, 사내 회의실에서 밋업(Meetup)과 같은 모임을 개최하도록 도와주어야 한다. 그들이 최첨단 기술스택을 선택하도록 장려하고, 자신들의 선택을 마음껏 외부에서 이야기하도록 북돋워야 한다. 프로그래밍을 도구가 아니라 비즈니스의 핵심 목적으로 삼을 때에만 가능한 이야기다.

 그러다보면 강호의 고수 개발자들이 관심을 갖고 서서히 모여든다. 고수들이 한 곳에 모이면 회사를 인터넷, 모바일 중심으로 재편하는 것이 문제가 아니다. 아무도 생각하지 못했던 혁신이 일어난다. 혁신이 몰고 올 파괴력을 생각하면 트래픽(즉 광고)을 끌어 모으는 것은 부수효과에 불과하다. 그러니까 개발자에게 부탁하라는 말은 이런 것을 의미한다.

 한국의 회사는 내부에 개발자를 두고 일을 개발자에게 부탁하는데 이상할 정도로 인색하다. 해외 업체에게 맡기거나 SI 서비스를 통해서 시스템을 구축한다. 그렇게 하는 것이 나은 경우도 물론 있다. 하지만 소프트웨어 개발 실력에 비즈니스 지식까지 통달한 사람이 갖는 생산성을 고려하면 그런 사람을 내부에 두는 것이 얼마나 필요한 일인지 깨닫지 못하는 것이 이상하다.

 간단하다. 그런 깨달음을 경쟁사보다 먼저 얻는 회사가 미래를 차지한다. 트래픽에 연연하는 것을 속물적이라고 비난해서는 곤란하다. 모든 일은 사람이 모인 곳에서 시작되기 때문이다. 트래픽은 21세기의 광장이다. 그래서 날이 갈수록 힘을 잃어가는 신문사들의 사활은 바로 여기에 달려있다. 지금, 개발자에게 부탁하라. 거기가 혁신의 출발점이다.

2015년 6월 17일 수요일

StackExchage.Resid.StrongName Test

https://github.com/StackExchange/StackExchange.Redis/tree/master/StackExchange.Redis.Tests

StackExchange.Resid.StrongName (MultiAdd Sample)

using System.Linq;
using NUnit.Framework;

namespace StackExchange.Redis.Tests
{
    [TestFixture]
    public class MultiAdd : TestBase
    {
        [Test]
        public void AddSortedSetEveryWay()
        {
            using(var conn = Create())
            {
                var db = conn.GetDatabase(3);

                RedisKey key = Me();
                db.KeyDelete(key);
                db.SortedSetAdd(key, "a", 1);
                db.SortedSetAdd(key, new[] {
                    new SortedSetEntry("b", 2) });
                db.SortedSetAdd(key, new[] {
                    new SortedSetEntry("c", 3),
                    new SortedSetEntry("d", 4)});
                db.SortedSetAdd(key, new[] {
                    new SortedSetEntry("e", 5),
                    new SortedSetEntry("f", 6),
                    new SortedSetEntry("g", 7)});
                db.SortedSetAdd(key, new[] {
                    new SortedSetEntry("h", 8),
                    new SortedSetEntry("i", 9),
                    new SortedSetEntry("j", 10),
                    new SortedSetEntry("k", 11)});
                var vals = db.SortedSetRangeByScoreWithScores(key);
                string s = string.Join(",", vals.OrderByDescending(x => x.Score).Select(x => x.Element));
                Assert.AreEqual("k,j,i,h,g,f,e,d,c,b,a", s);
                s = string.Join(",", vals.OrderBy(x => x.Score).Select(x => x.Score));
                Assert.AreEqual("1,2,3,4,5,6,7,8,9,10,11", s);
            }
        }

        [Test]
        public void AddHashEveryWay()
        {
            using (var conn = Create())
            {
                var db = conn.GetDatabase(3);

                RedisKey key = Me();
                db.KeyDelete(key);
                db.HashSet(key, "a", 1);
                db.HashSet(key, new[] {
                    new HashEntry("b", 2) });
                db.HashSet(key, new[] {
                    new HashEntry("c", 3),
                    new HashEntry("d", 4)});
                db.HashSet(key, new[] {
                    new HashEntry("e", 5),
                    new HashEntry("f", 6),
                    new HashEntry("g", 7)});
                db.HashSet(key, new[] {
                    new HashEntry("h", 8),
                    new HashEntry("i", 9),
                    new HashEntry("j", 10),
                    new HashEntry("k", 11)});
                var vals = db.HashGetAll(key);
                string s = string.Join(",", vals.OrderByDescending(x => (double)x.Value).Select(x => x.Name));
                Assert.AreEqual("k,j,i,h,g,f,e,d,c,b,a", s);
                s = string.Join(",", vals.OrderBy(x => (double)x.Value).Select(x => x.Value));
                Assert.AreEqual("1,2,3,4,5,6,7,8,9,10,11", s);
            }
        }

        [Test]
        public void AddSetEveryWay()
        {
            using (var conn = Create())
            {
                var db = conn.GetDatabase(3);

                RedisKey key = Me();
                db.KeyDelete(key);
                db.SetAdd(key, "a");
                db.SetAdd(key, new RedisValue[] { "b" });
                db.SetAdd(key, new RedisValue[] { "c", "d" });
                db.SetAdd(key, new RedisValue[] { "e", "f", "g" });
                db.SetAdd(key, new RedisValue[] { "h", "i", "j","k" });

                var vals = db.SetMembers(key);
                string s = string.Join(",", vals.OrderByDescending(x => x));
                Assert.AreEqual("k,j,i,h,g,f,e,d,c,b,a", s);
            }
        }

        [Test]
        public void AddSetEveryWayNumbers()
        {
            using (var conn = Create())
            {
                var db = conn.GetDatabase(3);

                RedisKey key = Me();
                db.KeyDelete(key);
                db.SetAdd(key, "a");
                db.SetAdd(key, new RedisValue[] { "1" });
                db.SetAdd(key, new RedisValue[] { "11", "2" });
                db.SetAdd(key, new RedisValue[] { "10", "3", "1.5" });
                db.SetAdd(key, new RedisValue[] { "2.2", "-1", "s", "t" });

                var vals = db.SetMembers(key);
                string s = string.Join(",", vals.OrderByDescending(x => x));
                Assert.AreEqual("t,s,a,11,10,3,2.2,2,1.5,1,-1", s);
            }
        }
    }
}

Redis with StackExchange.Redis.StrongName(Sample Code)


ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost");
db = redis.GetDatabase();
EndPoint ep = db.IdentifyEndpoint();
server = redis.GetServer(ep);

//Add Resid by Key-Value
RedisValue v = "a";
db.StringSet("stringKey", v);

Dictionary<RedisKey, RedisValue> dics = new Dictionary<RedisKey, RedisValue>() {
    {"stringKeyA", "S_1"},
    {"stringKeyB", "S_2"}
};
db.StringSet(dics.AsQueryable().ToArray(), When.Always, CommandFlags.None);

//Add to Resid by Set Value
RedisValue[] values = { "d", "e", "f" };
long size = db.SetAdd("SetKey", values);
IEnumerable<RedisValue> setvalue = db.SetScan("SetKey");
            
//Add to Redis by List Value
db.ListRightPush("ListKey", "abcdefghijklmnopqrstuvwxyz".Select(x => (RedisValue)x.ToString()).ToArray());
RedisValue[] listvalue = db.ListRange("ListKey", 0, -1);

//Key Confirm
foreach (RedisKey k in server.Keys()) {
    MessageBox.Show(k.ToString());
}

How to Insert Page Number as Footer in Word with C#/VB.NET

https://janewdaisy.wordpress.c...oter-in-word-document-cvb-net/

Text Alignments in Word 2010

https://msdn.microsoft.com/en-...s/library/office/gg278313.aspx

Node.js Oracle Driver(Created By Oracle)

https://blogs.oracle.com/opal/entry/introducing_node_oracledb_a_node

History of io.js

http://blog.outsider.ne.kr/1102

Authentication for ASP.NET

https://azure.microsoft.com/en-us/documentation/articles/web-sites-dotnet-deploy-aspnet-mvc-app-membership-oauth-sql-database/

ASP.NET with EF6

http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application